From 9ccba268c07771b4e6a8c6a4b9950966dd5201f5 Mon Sep 17 00:00:00 2001 From: Brett Slatkin Date: Fri, 14 Jun 2019 16:11:34 -0700 Subject: [PATCH 01/12] Making errata link to all issues, not just open ones --- Errata.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Errata.md b/Errata.md index a9eec2a..153d010 100644 --- a/Errata.md +++ b/Errata.md @@ -1,3 +1,3 @@ # Errata -You can see a list of errors that have been found in [_Effective Python_](http://www.effectivepython.com) by looking at the `Confirmed` label for open issues [following this link](https://github.com/bslatkin/effectivepython/issues?q=is%3Aopen+is%3Aissue+label%3AConfirmed). If you found a mistake and don't see it listed there, [please create a new issue](https://github.com/bslatkin/effectivepython/issues/new). Thanks for your help! +You can see a list of errors that have been found in [_Effective Python_](http://www.effectivepython.com) by looking at the `Confirmed` label for open issues [following this link](https://github.com/bslatkin/effectivepython/issues?q=is%3Aissue+label%3AConfirmed). If you found a mistake and don't see it listed there, [please create a new issue](https://github.com/bslatkin/effectivepython/issues/new). Thanks for your help! From ead664b9396af485389dc22d4d9a9c5c332d8e3e Mon Sep 17 00:00:00 2001 From: Brett Slatkin Date: Sat, 5 Oct 2019 14:56:05 -0700 Subject: [PATCH 02/12] Updates for second edition --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 668dc6b..a37db09 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # Effective Python -Hello! You've reached the official source code repository for _Effective Python_. To learn more about the book or contact the author, please [visit the official website](http://www.effectivepython.com). +Hello! You've reached the official source code repository for _Effective Python: Second Edition_. To learn more about the book or contact the author, please [visit the official website](https://effectivepython.com). -[![Cover](./cover.jpg)](http://www.effectivepython.com) +[![Cover](./cover.jpg)](https://effectivepython.com) In this repository you can browse all of the source code included in the book. Each item has its own file or directory containing the example code. Each file is annotated with which example snippet it came from within each chapter. -To run all the code for an item, just type `./item_01.py` into your shell and see what it prints out. Alternatively you can type `python3 item_01.py` or `python2.7 item_03_example_03.py` to run a specific version of Python. +To run all the code for an item, just type `./item_01.py` into your shell and see what it prints out. Alternatively you can type `python3 item_01.py` to run a specific version of Python. To report a problem with the book or view known issues, please [visit the Errata page](./Errata.md). From fbfc2168b3a188606c500c16f79dee44c72927dd Mon Sep 17 00:00:00 2001 From: Brett Slatkin Date: Sat, 5 Oct 2019 17:11:19 -0700 Subject: [PATCH 03/12] Updating docs to talk about second edition instead of first --- Errata.md | 2 +- VIDEO.md | 37 +-- cover.jpg | Bin 39862 -> 60618 bytes example_code/.gitignore | 3 - example_code/Makefile | 10 - example_code/item_01.py | 26 -- example_code/item_03.py | 60 ----- example_code/item_03_example_03.py | 32 --- example_code/item_03_example_04.py | 32 --- example_code/item_04.py | 88 ------ example_code/item_05.py | 97 ------- example_code/item_06.py | 67 ----- example_code/item_07.py | 49 ---- example_code/item_08.py | 67 ----- example_code/item_09.py | 49 ---- example_code/item_10.py | 51 ---- example_code/item_11.py | 66 ----- example_code/item_12.py | 85 ------ example_code/item_13.py | 105 -------- example_code/item_14.py | 96 ------- example_code/item_15.py | 104 ------- example_code/item_15_example_09.py | 38 --- example_code/item_16.py | 81 ------ example_code/item_17.py | 138 ---------- example_code/item_18.py | 73 ----- example_code/item_19.py | 106 -------- example_code/item_20.py | 100 ------- example_code/item_21.py | 112 -------- example_code/item_21_example_10.py | 28 -- example_code/item_21_example_11.py | 61 ----- example_code/item_22.py | 201 -------------- example_code/item_23.py | 112 -------- example_code/item_24.py | 182 ------------- example_code/item_25.py | 133 --------- example_code/item_25_example_09.py | 36 --- example_code/item_25_example_10.py | 43 --- example_code/item_26.py | 151 ----------- example_code/item_27.py | 188 ------------- example_code/item_28.py | 165 ------------ example_code/item_29.py | 164 ----------- example_code/item_30.py | 136 ---------- example_code/item_31.py | 193 ------------- example_code/item_32.py | 157 ----------- example_code/item_33.py | 72 ----- example_code/item_33_example_02.py | 34 --- example_code/item_34.py | 167 ------------ example_code/item_35.py | 95 ------- example_code/item_36.py | 129 --------- example_code/item_37.py | 103 ------- example_code/item_38.py | 106 -------- example_code/item_39.py | 248 ----------------- example_code/item_40.py | 255 ------------------ example_code/item_40_example_18.py | 34 --- example_code/item_40_example_19.py | 41 --- example_code/item_41.py | 59 ---- example_code/item_42.py | 108 -------- example_code/item_43.py | 98 ------- example_code/item_44.py | 190 ------------- example_code/item_45.py | 95 ------- example_code/item_46.py | 137 ---------- example_code/item_47.py | 68 ----- example_code/item_49.py | 88 ------ .../item_50/api_package/api_consumer.py | 38 --- example_code/item_50/api_package/main.py | 24 -- .../item_50/api_package/mypackage/__init__.py | 28 -- .../item_50/api_package/mypackage/models.py | 29 -- .../item_50/api_package/mypackage/utils.py | 34 --- .../namespace_package/analysis/__init__.py | 24 -- .../namespace_package/analysis/utils.py | 29 -- .../namespace_package/frontend/__init__.py | 24 -- .../namespace_package/frontend/utils.py | 28 -- .../item_50/namespace_package/main.py | 28 -- .../item_50/namespace_package/main2.py | 27 -- .../item_50/namespace_package/main3.py | 29 -- example_code/item_51.py | 131 --------- .../item_52/recursive_import_bad/app.py | 31 --- .../item_52/recursive_import_bad/dialog.py | 33 --- .../item_52/recursive_import_bad/main.py | 24 -- .../item_52/recursive_import_dynamic/app.py | 31 --- .../recursive_import_dynamic/dialog.py | 38 --- .../item_52/recursive_import_dynamic/main.py | 24 -- .../recursive_import_nosideeffects/app.py | 33 --- .../recursive_import_nosideeffects/dialog.py | 36 --- .../recursive_import_nosideeffects/main.py | 30 --- .../item_52/recursive_import_ordering/app.py | 31 --- .../recursive_import_ordering/dialog.py | 33 --- .../item_52/recursive_import_ordering/main.py | 24 -- example_code/item_54.py | 36 --- .../item_54/module_scope/db_connection.py | 36 --- example_code/item_54/module_scope/dev_main.py | 26 -- .../item_54/module_scope/prod_main.py | 26 -- example_code/item_55.py | 82 ------ example_code/item_56.py | 31 --- example_code/item_56/testing/utils.py | 31 --- example_code/item_56/testing/utils_test.py | 38 --- example_code/item_58.py | 114 -------- example_code/item_59/tracemalloc/top_n.py | 34 --- example_code/item_59/tracemalloc/using_gc.py | 33 --- .../item_59/tracemalloc/waste_memory.py | 43 --- .../item_59/tracemalloc/with_trace.py | 33 --- 100 files changed, 3 insertions(+), 7252 deletions(-) delete mode 100644 example_code/.gitignore delete mode 100644 example_code/Makefile delete mode 100755 example_code/item_01.py delete mode 100755 example_code/item_03.py delete mode 100755 example_code/item_03_example_03.py delete mode 100755 example_code/item_03_example_04.py delete mode 100755 example_code/item_04.py delete mode 100755 example_code/item_05.py delete mode 100755 example_code/item_06.py delete mode 100755 example_code/item_07.py delete mode 100755 example_code/item_08.py delete mode 100755 example_code/item_09.py delete mode 100755 example_code/item_10.py delete mode 100755 example_code/item_11.py delete mode 100755 example_code/item_12.py delete mode 100755 example_code/item_13.py delete mode 100755 example_code/item_14.py delete mode 100755 example_code/item_15.py delete mode 100755 example_code/item_15_example_09.py delete mode 100755 example_code/item_16.py delete mode 100755 example_code/item_17.py delete mode 100755 example_code/item_18.py delete mode 100755 example_code/item_19.py delete mode 100755 example_code/item_20.py delete mode 100755 example_code/item_21.py delete mode 100755 example_code/item_21_example_10.py delete mode 100755 example_code/item_21_example_11.py delete mode 100755 example_code/item_22.py delete mode 100755 example_code/item_23.py delete mode 100755 example_code/item_24.py delete mode 100755 example_code/item_25.py delete mode 100755 example_code/item_25_example_09.py delete mode 100755 example_code/item_25_example_10.py delete mode 100755 example_code/item_26.py delete mode 100755 example_code/item_27.py delete mode 100755 example_code/item_28.py delete mode 100755 example_code/item_29.py delete mode 100755 example_code/item_30.py delete mode 100755 example_code/item_31.py delete mode 100755 example_code/item_32.py delete mode 100755 example_code/item_33.py delete mode 100755 example_code/item_33_example_02.py delete mode 100755 example_code/item_34.py delete mode 100755 example_code/item_35.py delete mode 100755 example_code/item_36.py delete mode 100755 example_code/item_37.py delete mode 100755 example_code/item_38.py delete mode 100755 example_code/item_39.py delete mode 100755 example_code/item_40.py delete mode 100755 example_code/item_40_example_18.py delete mode 100755 example_code/item_40_example_19.py delete mode 100755 example_code/item_41.py delete mode 100755 example_code/item_42.py delete mode 100755 example_code/item_43.py delete mode 100755 example_code/item_44.py delete mode 100755 example_code/item_45.py delete mode 100755 example_code/item_46.py delete mode 100755 example_code/item_47.py delete mode 100755 example_code/item_49.py delete mode 100755 example_code/item_50/api_package/api_consumer.py delete mode 100755 example_code/item_50/api_package/main.py delete mode 100755 example_code/item_50/api_package/mypackage/__init__.py delete mode 100755 example_code/item_50/api_package/mypackage/models.py delete mode 100755 example_code/item_50/api_package/mypackage/utils.py delete mode 100755 example_code/item_50/namespace_package/analysis/__init__.py delete mode 100755 example_code/item_50/namespace_package/analysis/utils.py delete mode 100755 example_code/item_50/namespace_package/frontend/__init__.py delete mode 100755 example_code/item_50/namespace_package/frontend/utils.py delete mode 100755 example_code/item_50/namespace_package/main.py delete mode 100755 example_code/item_50/namespace_package/main2.py delete mode 100755 example_code/item_50/namespace_package/main3.py delete mode 100755 example_code/item_51.py delete mode 100755 example_code/item_52/recursive_import_bad/app.py delete mode 100755 example_code/item_52/recursive_import_bad/dialog.py delete mode 100755 example_code/item_52/recursive_import_bad/main.py delete mode 100755 example_code/item_52/recursive_import_dynamic/app.py delete mode 100755 example_code/item_52/recursive_import_dynamic/dialog.py delete mode 100755 example_code/item_52/recursive_import_dynamic/main.py delete mode 100755 example_code/item_52/recursive_import_nosideeffects/app.py delete mode 100755 example_code/item_52/recursive_import_nosideeffects/dialog.py delete mode 100755 example_code/item_52/recursive_import_nosideeffects/main.py delete mode 100755 example_code/item_52/recursive_import_ordering/app.py delete mode 100755 example_code/item_52/recursive_import_ordering/dialog.py delete mode 100755 example_code/item_52/recursive_import_ordering/main.py delete mode 100755 example_code/item_54.py delete mode 100755 example_code/item_54/module_scope/db_connection.py delete mode 100755 example_code/item_54/module_scope/dev_main.py delete mode 100755 example_code/item_54/module_scope/prod_main.py delete mode 100755 example_code/item_55.py delete mode 100755 example_code/item_56.py delete mode 100755 example_code/item_56/testing/utils.py delete mode 100755 example_code/item_56/testing/utils_test.py delete mode 100755 example_code/item_58.py delete mode 100755 example_code/item_59/tracemalloc/top_n.py delete mode 100755 example_code/item_59/tracemalloc/using_gc.py delete mode 100755 example_code/item_59/tracemalloc/waste_memory.py delete mode 100755 example_code/item_59/tracemalloc/with_trace.py diff --git a/Errata.md b/Errata.md index 153d010..3fcfc93 100644 --- a/Errata.md +++ b/Errata.md @@ -1,3 +1,3 @@ # Errata -You can see a list of errors that have been found in [_Effective Python_](http://www.effectivepython.com) by looking at the `Confirmed` label for open issues [following this link](https://github.com/bslatkin/effectivepython/issues?q=is%3Aissue+label%3AConfirmed). If you found a mistake and don't see it listed there, [please create a new issue](https://github.com/bslatkin/effectivepython/issues/new). Thanks for your help! +You can see a list of errors that have been found in [_Effective Python: Second Edition_](https://effectivepython.com) by looking at the `Confirmed` label for open issues [following this link](https://github.com/bslatkin/effectivepython/issues?utf8=✓&q=label%3A2ed+label%3Aconfirmed). If you found a mistake and don't see it listed there, [please create a new issue](https://github.com/bslatkin/effectivepython/issues/new). Thanks for your help! diff --git a/VIDEO.md b/VIDEO.md index 50130ed..1e902b8 100644 --- a/VIDEO.md +++ b/VIDEO.md @@ -1,38 +1,5 @@ # Example code for [Effective Python LiveLessons](http://www.informit.com/store/effective-python-livelessons-video-training-downloadable-9780134175164) -These items are numbered differently than in the book. Below are links to each item's corresponding piece of example code. +**This is for a previous edition of the book.** -To run all the code for an item, just type ./item_01.py into your shell and see what it prints out. Alternatively you can type python3 item_01.py or python2.7 item_03_example_03.py to run a specific version of Python. - -- "[Item 1 Know how to slice sequences](example_code/item_05.py)" -- "[Item 2 Avoid using start, end, and stride in a single slice](example_code/item_05.py)" -- "[Item 3 Prefer enumerate over range](example_code/item_10.py)" -- "[Item 4 Use zip to process iterators in parallel](example_code/item_11.py)" -- "[Item 5 Avoid else blocks after for and while loops](example_code/item_12.py)" -- "[Item 6 Take advantage of each block in try/except/else/finally](example_code/item_13.py)" -- "[Item 7 Consider contextlib and with statements for reusable try/finally behavior](example_code/item_43.py)" -- "[Item 8 Use list comprehensions instead of map and filter](example_code/item_07.py)" -- "[Item 9 Avoid more than two expressions in list comprehensions](example_code/item_08.py)" -- "[Item 10 Consider generator expressions for large comprehensions](example_code/item_09.py)" -- "[Item 11 Consider generators instead of returning lists](example_code/item_16.py)" -- "[Item 12 Be defensive when iterating over arguments ](example_code/item_17.py)" -- "[Item 13 Know how closures interact with variable scope](example_code/item_15.py)" and [example for Python 2](example_code/item_15_example_09.py) -- "[Item 14 Accept functions for simple interfaces instead of classes](example_code/item_23.py)" -- "[Item 15 Reduce visual noise with variable positional arguments](example_code/item_19.py)" -- "[Item 16 Provide optional behavior with keyword arguments](example_code/item_19.py)" -- "[Item 17 Enforce clarity with keyword-only arguments](example_code/item_21.py)" -- "[Item 18 Use None and docstrings to specify dynamic default arguments](example_code/item_20.py)" -- "[Item 19 Prefer helper classes over bookkeeping with dictionaries and tuples](example_code/item_22.py)" -- "[Item 20 Use plain attributes instead of get and set methods ](example_code/item_29.py)" -- "[Item 21 Prefer public attributes over private ones](example_code/item_27.py)" -- "[Item 22 Use @classmethod polymorphism to construct objects generically](example_code/item_24.py)" -- "[Item 23 Use subprocess to manage child processes](example_code/item_36.py)" -- "[Item 24 Use threads for blocking I/O, avoid for parallelism](example_code/item_37.py)" -- "[Item 25 Use Lock to prevent data races in threads](example_code/item_38.py)" -- "[Item 26 Use Queue to coordinate work between threads](example_code/item_39.py)" -- "[Item 27 Consider concurrent.futures for true parallelism](example_code/item_41.py)" -- "Item 28 Use virtual environments for isolated and reproducible dependencies" has no example code -- "[Item 29 Test everything with unittest](example_code/item_56.py) and [a directory of tests](example_code/item_56/testing)" -- "Item 30 Consider interactive debugging with pdb" has no example code -- "[Item 31 Profile before optimizing](example_code/item_58.py)" -- "[Item 32 Use tracemalloc to understand memory usage and leaks](example_code/item_59/tracemalloc)" +[Go here to see the corresponding example code](../v1/VIDEO.md) diff --git a/cover.jpg b/cover.jpg index f27ca615bcf9458f596a677dc33b73bcda693d01..1495e76faea7ab30a2b308c18e75ee549918a91a 100644 GIT binary patch literal 60618 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 literal 39862 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 Jtkr*x{~Hj>*yaEL diff --git a/example_code/.gitignore b/example_code/.gitignore deleted file mode 100644 index b8b2cb7..0000000 --- a/example_code/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.txt -*.bin -*.json diff --git a/example_code/Makefile b/example_code/Makefile deleted file mode 100644 index 722775a..0000000 --- a/example_code/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -test: - for path in $$(ls ./*.py); \ - do \ - echo "\n\nRunning $$path"; \ - $$path 0 4 if x % 2 == 0] -c = [x for x in a if x > 4 and x % 2 == 0] -print(b) -print(c) -assert b and c -assert b == c - - -# Example 6 -matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] -filtered = [[x for x in row if x % 3 == 0] - for row in matrix if sum(row) >= 10] -print(filtered) diff --git a/example_code/item_09.py b/example_code/item_09.py deleted file mode 100755 index 6d3a41a..0000000 --- a/example_code/item_09.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -import random -with open('my_file.txt', 'w') as f: - for _ in range(10): - f.write('a' * random.randint(0, 100)) - f.write('\n') - -value = [len(x) for x in open('my_file.txt')] -print(value) - - -# Example 2 -it = (len(x) for x in open('my_file.txt')) -print(it) - - -# Example 3 -print(next(it)) -print(next(it)) - - -# Example 4 -roots = ((x, x**0.5) for x in it) - - -# Example 5 -print(next(roots)) diff --git a/example_code/item_10.py b/example_code/item_10.py deleted file mode 100755 index fb244be..0000000 --- a/example_code/item_10.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -from random import randint -random_bits = 0 -for i in range(64): - if randint(0, 1): - random_bits |= 1 << i -print(bin(random_bits)) - - -# Example 2 -flavor_list = ['vanilla', 'chocolate', 'pecan', 'strawberry'] -for flavor in flavor_list: - print('%s is delicious' % flavor) - - -# Example 3 -for i in range(len(flavor_list)): - flavor = flavor_list[i] - print('%d: %s' % (i + 1, flavor)) - - -# Example 4 -for i, flavor in enumerate(flavor_list): - print('%d: %s' % (i + 1, flavor)) - - -# Example 5 -for i, flavor in enumerate(flavor_list, 1): - print('%d: %s' % (i, flavor)) diff --git a/example_code/item_11.py b/example_code/item_11.py deleted file mode 100755 index 09cffa5..0000000 --- a/example_code/item_11.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -names = ['Cecilia', 'Lise', 'Marie'] -letters = [len(n) for n in names] -print(letters) - - -# Example 2 -longest_name = None -max_letters = 0 - -for i in range(len(names)): - count = letters[i] - if count > max_letters: - longest_name = names[i] - max_letters = count - -print(longest_name) - - -# Example 3 -longest_name = None -max_letters = 0 -for i, name in enumerate(names): - count = letters[i] - if count > max_letters: - longest_name = name - max_letters = count -print(longest_name) - - -# Example 4 -longest_name = None -max_letters = 0 -for name, count in zip(names, letters): - if count > max_letters: - longest_name = name - max_letters = count -print(longest_name) - - -# Example 5 -names.append('Rosalind') -for name, count in zip(names, letters): - print(name) diff --git a/example_code/item_12.py b/example_code/item_12.py deleted file mode 100755 index 0e7a262..0000000 --- a/example_code/item_12.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -for i in range(3): - print('Loop %d' % i) -else: - print('Else block!') - - -# Example 2 -for i in range(3): - print('Loop %d' % i) - if i == 1: - break -else: - print('Else block!') - - -# Example 3 -for x in []: - print('Never runs') -else: - print('For Else block!') - - -# Example 4 -while False: - print('Never runs') -else: - print('While Else block!') - - -# Example 5 -a = 4 -b = 9 - -for i in range(2, min(a, b) + 1): - print('Testing', i) - if a % i == 0 and b % i == 0: - print('Not coprime') - break -else: - print('Coprime') - - -# Example 6 -def coprime(a, b): - for i in range(2, min(a, b) + 1): - if a % i == 0 and b % i == 0: - return False - return True -print(coprime(4, 9)) -print(coprime(3, 6)) - - -# Example 7 -def coprime2(a, b): - is_coprime = True - for i in range(2, min(a, b) + 1): - if a % i == 0 and b % i == 0: - is_coprime = False - break - return is_coprime -print(coprime2(4, 9)) -print(coprime2(3, 6)) diff --git a/example_code/item_13.py b/example_code/item_13.py deleted file mode 100755 index d553a51..0000000 --- a/example_code/item_13.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -handle = open('random_data.txt', 'w', encoding='utf-8') -handle.write('success\nand\nnew\nlines') -handle.close() -handle = open('random_data.txt') # May raise IOError -try: - data = handle.read() # May raise UnicodeDecodeError -finally: - handle.close() # Always runs after try: - - -# Example 2 -import json - -def load_json_key(data, key): - try: - result_dict = json.loads(data) # May raise ValueError - except ValueError as e: - raise KeyError from e - else: - return result_dict[key] # May raise KeyError - -# JSON decode successful -assert load_json_key('{"foo": "bar"}', 'foo') == 'bar' -try: - load_json_key('{"foo": "bar"}', 'does not exist') - assert False -except KeyError: - pass # Expected - -# JSON decode fails -try: - load_json_key('{"foo": bad payload', 'foo') - assert False -except KeyError: - pass # Expected - - -# Example 3 -import json -UNDEFINED = object() - -def divide_json(path): - handle = open(path, 'r+') # May raise IOError - try: - data = handle.read() # May raise UnicodeDecodeError - op = json.loads(data) # May raise ValueError - value = ( - op['numerator'] / - op['denominator']) # May raise ZeroDivisionError - except ZeroDivisionError as e: - return UNDEFINED - else: - op['result'] = value - result = json.dumps(op) - handle.seek(0) - handle.write(result) # May raise IOError - return value - finally: - handle.close() # Always runs - -# Everything works -temp_path = 'random_data.json' -handle = open(temp_path, 'w') -handle.write('{"numerator": 1, "denominator": 10}') -handle.close() -assert divide_json(temp_path) == 0.1 - -# Divide by Zero error -handle = open(temp_path, 'w') -handle.write('{"numerator": 1, "denominator": 0}') -handle.close() -assert divide_json(temp_path) is UNDEFINED - -# JSON decode error -handle = open(temp_path, 'w') -handle.write('{"numerator": 1 bad data') -handle.close() -try: - divide_json(temp_path) - assert False -except ValueError: - pass # Expected diff --git a/example_code/item_14.py b/example_code/item_14.py deleted file mode 100755 index bb00554..0000000 --- a/example_code/item_14.py +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def divide(a, b): - try: - return a / b - except ZeroDivisionError: - return None - -assert divide(4, 2) == 2 -assert divide(0, 1) == 0 -assert divide(3, 6) == 0.5 -assert divide(1, 0) == None - - -# Example 2 -x, y = 1, 0 -result = divide(x, y) -if result is None: - print('Invalid inputs') -else: - print('Result is %.1f' % result) - - -# Example 3 -x, y = 0, 5 -result = divide(x, y) -if not result: - print('Invalid inputs') # This is wrong! -else: - assert False - - -# Example 4 -def divide(a, b): - try: - return True, a / b - except ZeroDivisionError: - return False, None - - -# Example 5 -x, y = 5, 0 -success, result = divide(x, y) -if not success: - print('Invalid inputs') - - -# Example 6 -x, y = 5, 0 -_, result = divide(x, y) -if not result: - print('Invalid inputs') # This is right - -x, y = 0, 5 -_, result = divide(x, y) -if not result: - print('Invalid inputs') # This is wrong - - -# Example 7 -def divide(a, b): - try: - return a / b - except ZeroDivisionError as e: - raise ValueError('Invalid inputs') from e - - -# Example 8 -x, y = 5, 2 -try: - result = divide(x, y) -except ValueError: - print('Invalid inputs') -else: - print('Result is %.1f' % result) diff --git a/example_code/item_15.py b/example_code/item_15.py deleted file mode 100755 index 5536610..0000000 --- a/example_code/item_15.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def sort_priority(values, group): - def helper(x): - if x in group: - return (0, x) - return (1, x) - values.sort(key=helper) - - -# Example 2 -numbers = [8, 3, 1, 2, 5, 4, 7, 6] -group = {2, 3, 5, 7} -sort_priority(numbers, group) -print(numbers) - - -# Example 3 -def sort_priority2(numbers, group): - found = False - def helper(x): - if x in group: - found = True # Seems simple - return (0, x) - return (1, x) - numbers.sort(key=helper) - return found - - -# Example 4 -found = sort_priority2(numbers, group) -print('Found:', found) -print(numbers) - - -# Example 5 -def sort_priority2(numbers, group): - found = False # Scope: 'sort_priority2' - def helper(x): - if x in group: - found = True # Scope: 'helper' -- Bad! - return (0, x) - return (1, x) - numbers.sort(key=helper) - return found - - -# Example 6 -def sort_priority3(numbers, group): - found = False - def helper(x): - nonlocal found - if x in group: - found = True - return (0, x) - return (1, x) - numbers.sort(key=helper) - return found - - -# Example 7 -found = sort_priority3(numbers, group) -print('Found:', found) -print(numbers) - - -# Example 8 -class Sorter(object): - def __init__(self, group): - self.group = group - self.found = False - - def __call__(self, x): - if x in self.group: - self.found = True - return (0, x) - return (1, x) - -sorter = Sorter(group) -numbers.sort(key=sorter) -assert sorter.found is True -print('Found:', found) -print(numbers) diff --git a/example_code/item_15_example_09.py b/example_code/item_15_example_09.py deleted file mode 100755 index 474d64e..0000000 --- a/example_code/item_15_example_09.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 9 -def sort_priority(numbers, group): - found = [False] - def helper(x): - if x in group: - found[0] = True - return (0, x) - return (1, x) - numbers.sort(key=helper) - return found[0] - -numbers = [8, 3, 1, 2, 5, 4, 7, 6] -group = set([2, 3, 5, 7]) -found = sort_priority(numbers, group) -print('Found:', found) -print(numbers) diff --git a/example_code/item_16.py b/example_code/item_16.py deleted file mode 100755 index c5defa9..0000000 --- a/example_code/item_16.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def index_words(text): - result = [] - if text: - result.append(0) - for index, letter in enumerate(text): - if letter == ' ': - result.append(index + 1) - return result - - -# Example 2 -address = 'Four score and seven years ago...' -address = 'Four score and seven years ago our fathers brought forth on this continent a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal.' -result = index_words(address) -print(result[:3]) - - -# Example 3 -def index_words_iter(text): - if text: - yield 0 - for index, letter in enumerate(text): - if letter == ' ': - yield index + 1 - - -# Example 4 -result = list(index_words_iter(address)) -print(result[:3]) - - -# Example 5 -def index_file(handle): - offset = 0 - for line in handle: - if line: - yield offset - for letter in line: - offset += 1 - if letter == ' ': - yield offset - - -# Example 6 -address_lines = """Four score and seven years -ago our fathers brought forth on this -continent a new nation, conceived in liberty, -and dedicated to the proposition that all men -are created equal.""" - -with open('address.txt', 'w') as f: - f.write(address_lines) - -from itertools import islice -with open('address.txt', 'r') as f: - it = index_file(f) - results = islice(it, 0, 3) - print(list(results)) diff --git a/example_code/item_17.py b/example_code/item_17.py deleted file mode 100755 index 75fd7e4..0000000 --- a/example_code/item_17.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def normalize(numbers): - total = sum(numbers) - result = [] - for value in numbers: - percent = 100 * value / total - result.append(percent) - return result - - -# Example 2 -visits = [15, 35, 80] -percentages = normalize(visits) -print(percentages) - - -# Example 3 -path = 'my_numbers.txt' -with open(path, 'w') as f: - for i in (15, 35, 80): - f.write('%d\n' % i) - -def read_visits(data_path): - with open(data_path) as f: - for line in f: - yield int(line) - - -# Example 4 -it = read_visits('my_numbers.txt') -percentages = normalize(it) -print(percentages) - - -# Example 5 -it = read_visits('my_numbers.txt') -print(list(it)) -print(list(it)) # Already exhausted - - -# Example 6 -def normalize_copy(numbers): - numbers = list(numbers) # Copy the iterator - total = sum(numbers) - result = [] - for value in numbers: - percent = 100 * value / total - result.append(percent) - return result - - -# Example 7 -it = read_visits('my_numbers.txt') -percentages = normalize_copy(it) -print(percentages) - - -# Example 8 -def normalize_func(get_iter): - total = sum(get_iter()) # New iterator - result = [] - for value in get_iter(): # New iterator - percent = 100 * value / total - result.append(percent) - return result - - -# Example 9 -percentages = normalize_func(lambda: read_visits(path)) -print(percentages) - - -# Example 10 -class ReadVisits(object): - def __init__(self, data_path): - self.data_path = data_path - - def __iter__(self): - with open(self.data_path) as f: - for line in f: - yield int(line) - - -# Example 11 -visits = ReadVisits(path) -percentages = normalize(visits) -print(percentages) - - -# Example 12 -def normalize_defensive(numbers): - if iter(numbers) is iter(numbers): # An iterator -- bad! - raise TypeError('Must supply a container') - total = sum(numbers) - result = [] - for value in numbers: - percent = 100 * value / total - result.append(percent) - return result - - -# Example 13 -visits = [15, 35, 80] -normalize_defensive(visits) # No error -visits = ReadVisits(path) -normalize_defensive(visits) # No error - - -# Example 14 -try: - it = iter(visits) - normalize_defensive(it) -except: - logging.exception('Expected') -else: - assert False diff --git a/example_code/item_18.py b/example_code/item_18.py deleted file mode 100755 index f3ac9e2..0000000 --- a/example_code/item_18.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def log(message, values): - if not values: - print(message) - else: - values_str = ', '.join(str(x) for x in values) - print('%s: %s' % (message, values_str)) - -log('My numbers are', [1, 2]) -log('Hi there', []) - - -# Example 2 -def log(message, *values): # The only difference - if not values: - print(message) - else: - values_str = ', '.join(str(x) for x in values) - print('%s: %s' % (message, values_str)) - -log('My numbers are', 1, 2) -log('Hi there') # Much better - - -# Example 3 -favorites = [7, 33, 99] -log('Favorite colors', *favorites) - - -# Example 4 -def my_generator(): - for i in range(10): - yield i - -def my_func(*args): - print(args) - -it = my_generator() -my_func(*it) - - -# Example 5 -def log(sequence, message, *values): - if not values: - print('%s: %s' % (sequence, message)) - else: - values_str = ', '.join(str(x) for x in values) - print('%s: %s: %s' % (sequence, message, values_str)) - -log(1, 'Favorites', 7, 33) # New usage is OK -log('Favorite numbers', 7, 33) # Old usage breaks diff --git a/example_code/item_19.py b/example_code/item_19.py deleted file mode 100755 index bc529fa..0000000 --- a/example_code/item_19.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def remainder(number, divisor): - return number % divisor - -assert remainder(20, 7) == 6 - - -# Example 2 -remainder(20, 7) -remainder(20, divisor=7) -remainder(number=20, divisor=7) -remainder(divisor=7, number=20) - - -# Example 3 -try: - # This will not compile - source = """remainder(number=20, 7)""" - eval(source) -except: - logging.exception('Expected') -else: - assert False - - -# Example 4 -try: - remainder(20, number=7) -except: - logging.exception('Expected') -else: - assert False - - -# Example 5 -def flow_rate(weight_diff, time_diff): - return weight_diff / time_diff - -weight_diff = 0.5 -time_diff = 3 -flow = flow_rate(weight_diff, time_diff) -print('%.3f kg per second' % flow) -assert (flow - 0.16666666666666666) < 0.0001 - - -# Example 6 -def flow_rate(weight_diff, time_diff, period): - return (weight_diff / time_diff) * period - - -# Example 7 -flow_per_second = flow_rate(weight_diff, time_diff, 1) -assert (flow_per_second - 0.16666666666666666) < 0.0001 - - -# Example 8 -def flow_rate(weight_diff, time_diff, period=1): - return (weight_diff / time_diff) * period - - -# Example 9 -flow_per_second = flow_rate(weight_diff, time_diff) -assert (flow_per_second - 0.16666666666666666) < 0.0001 -flow_per_hour = flow_rate(weight_diff, time_diff, period=3600) -assert flow_per_hour == 600.0 - - -# Example 10 -def flow_rate(weight_diff, time_diff, - period=1, units_per_kg=1): - return ((weight_diff * units_per_kg) / time_diff) * period - - -# Example 11 -pounds_per_hour = flow_rate(weight_diff, time_diff, - period=3600, units_per_kg=2.2) -print(pounds_per_hour) -assert pounds_per_hour == 1320.0 - - -# Example 12 -pounds_per_hour = flow_rate(weight_diff, time_diff, 3600, 2.2) -print(pounds_per_hour) -assert pounds_per_hour == 1320.0 diff --git a/example_code/item_20.py b/example_code/item_20.py deleted file mode 100755 index d176320..0000000 --- a/example_code/item_20.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -from time import sleep -from datetime import datetime - -def log(message, when=datetime.now()): - print('%s: %s' % (when, message)) - -log('Hi there!') -sleep(0.1) -log('Hi again!') - - -# Example 2 -def log(message, when=None): - """Log a message with a timestamp. - - Args: - message: Message to print. - when: datetime of when the message occurred. - Defaults to the present time. - """ - when = datetime.now() if when is None else when - print('%s: %s' % (when, message)) - - -# Example 3 -log('Hi there!') -sleep(0.1) -log('Hi again!') - - -# Example 4 -import json - -def decode(data, default={}): - try: - return json.loads(data) - except ValueError: - return default - - -# Example 5 -foo = decode('bad data') -foo['stuff'] = 5 -bar = decode('also bad') -bar['meep'] = 1 -print('Foo:', foo) -print('Bar:', bar) - - -# Example 6 -assert foo is bar - - -# Example 7 -def decode(data, default=None): - """Load JSON data from a string. - - Args: - data: JSON data to decode. - default: Value to return if decoding fails. - Defaults to an empty dictionary. - """ - if default is None: - default = {} - try: - return json.loads(data) - except ValueError: - return default - - -# Example 8 -foo = decode('bad data') -foo['stuff'] = 5 -bar = decode('also bad') -bar['meep'] = 1 -print('Foo:', foo) -print('Bar:', bar) diff --git a/example_code/item_21.py b/example_code/item_21.py deleted file mode 100755 index d8cc7c4..0000000 --- a/example_code/item_21.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def safe_division(number, divisor, ignore_overflow, - ignore_zero_division): - try: - return number / divisor - except OverflowError: - if ignore_overflow: - return 0 - else: - raise - except ZeroDivisionError: - if ignore_zero_division: - return float('inf') - else: - raise - - -# Example 2 -result = safe_division(1.0, 10**500, True, False) -print(result) -assert result is 0 - - -# Example 3 -result = safe_division(1.0, 0, False, True) -print(result) -assert result == float('inf') - - -# Example 4 -def safe_division_b(number, divisor, - ignore_overflow=False, - ignore_zero_division=False): - try: - return number / divisor - except OverflowError: - if ignore_overflow: - return 0 - else: - raise - except ZeroDivisionError: - if ignore_zero_division: - return float('inf') - else: - raise - - -# Example 5 -assert safe_division_b(1.0, 10**500, ignore_overflow=True) is 0 -assert safe_division_b(1.0, 0, ignore_zero_division=True) == float('inf') - - -# Example 6 -assert safe_division_b(1.0, 10**500, True, False) is 0 - - -# Example 7 -def safe_division_c(number, divisor, *, - ignore_overflow=False, - ignore_zero_division=False): - try: - return number / divisor - except OverflowError: - if ignore_overflow: - return 0 - else: - raise - except ZeroDivisionError: - if ignore_zero_division: - return float('inf') - else: - raise - - -# Example 8 -try: - safe_division_c(1.0, 10**500, True, False) -except: - logging.exception('Expected') -else: - assert False - - -# Example 9 -safe_division_c(1.0, 0, ignore_zero_division=True) # No exception -try: - safe_division_c(1.0, 0) - assert False -except ZeroDivisionError: - pass # Expected diff --git a/example_code/item_21_example_10.py b/example_code/item_21_example_10.py deleted file mode 100755 index de987a5..0000000 --- a/example_code/item_21_example_10.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 10 -def print_args(*args, **kwargs): - print 'Positional:', args - print 'Keyword: ', kwargs - -print_args(1, 2, foo='bar', stuff='meep') diff --git a/example_code/item_21_example_11.py b/example_code/item_21_example_11.py deleted file mode 100755 index 5788e25..0000000 --- a/example_code/item_21_example_11.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 11 -def safe_division_d(number, divisor, **kwargs): - ignore_overflow = kwargs.pop('ignore_overflow', False) - ignore_zero_div = kwargs.pop('ignore_zero_division', False) - if kwargs: - raise TypeError('Unexpected **kwargs: %r' % kwargs) - try: - return number / divisor - except OverflowError: - if ignore_overflow: - return 0 - else: - raise - except ZeroDivisionError: - if ignore_zero_div: - return float('inf') - else: - raise - -assert safe_division_d(1.0, 10) == 0.1 -assert safe_division_d(1.0, 0, ignore_zero_division=True) == float('inf') -assert safe_division_d(1.0, 10**500, ignore_overflow=True) is 0 - -# Example 12 -try: - safe_division_d(1.0, 0, False, True) -except: - logging.exception('Expected') -else: - assert False - - -# Example 13 -try: - safe_division_d(0.0, 0, unexpected=True) -except: - logging.exception('Expected') -else: - assert False diff --git a/example_code/item_22.py b/example_code/item_22.py deleted file mode 100755 index 3c31804..0000000 --- a/example_code/item_22.py +++ /dev/null @@ -1,201 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class SimpleGradebook(object): - def __init__(self): - self._grades = {} - - def add_student(self, name): - self._grades[name] = [] - - def report_grade(self, name, score): - self._grades[name].append(score) - - def average_grade(self, name): - grades = self._grades[name] - return sum(grades) / len(grades) - - -# Example 2 -book = SimpleGradebook() -book.add_student('Isaac Newton') -book.report_grade('Isaac Newton', 90) -book.report_grade('Isaac Newton', 95) -book.report_grade('Isaac Newton', 85) -print(book.average_grade('Isaac Newton')) - - -# Example 3 -class BySubjectGradebook(object): - def __init__(self): - self._grades = {} - - def add_student(self, name): - self._grades[name] = {} - - -# Example 4 - def report_grade(self, name, subject, grade): - by_subject = self._grades[name] - grade_list = by_subject.setdefault(subject, []) - grade_list.append(grade) - - def average_grade(self, name): - by_subject = self._grades[name] - total, count = 0, 0 - for grades in by_subject.values(): - total += sum(grades) - count += len(grades) - return total / count - - -# Example 5 -book = BySubjectGradebook() -book.add_student('Albert Einstein') -book.report_grade('Albert Einstein', 'Math', 75) -book.report_grade('Albert Einstein', 'Math', 65) -book.report_grade('Albert Einstein', 'Gym', 90) -book.report_grade('Albert Einstein', 'Gym', 95) -print(book.average_grade('Albert Einstein')) - - -# Example 6 -class WeightedGradebook(object): - def __init__(self): - self._grades = {} - - def add_student(self, name): - self._grades[name] = {} - - def report_grade(self, name, subject, score, weight): - by_subject = self._grades[name] - grade_list = by_subject.setdefault(subject, []) - grade_list.append((score, weight)) - - -# Example 7 - def average_grade(self, name): - by_subject = self._grades[name] - score_sum, score_count = 0, 0 - for subject, scores in by_subject.items(): - subject_avg, total_weight = 0, 0 - for score, weight in scores: - subject_avg += score * weight - total_weight += weight - score_sum += subject_avg / total_weight - score_count += 1 - return score_sum / score_count - - -# Example 8 -book = WeightedGradebook() -book.add_student('Albert Einstein') -book.report_grade('Albert Einstein', 'Math', 80, 0.10) -book.report_grade('Albert Einstein', 'Math', 80, 0.10) -book.report_grade('Albert Einstein', 'Math', 70, 0.80) -book.report_grade('Albert Einstein', 'Gym', 100, 0.40) -book.report_grade('Albert Einstein', 'Gym', 85, 0.60) -print(book.average_grade('Albert Einstein')) - - -# Example 9 -grades = [] -grades.append((95, 0.45)) -grades.append((85, 0.55)) -total = sum(score * weight for score, weight in grades) -total_weight = sum(weight for _, weight in grades) -average_grade = total / total_weight -print(average_grade) - - -# Example 10 -grades = [] -grades.append((95, 0.45, 'Great job')) -grades.append((85, 0.55, 'Better next time')) -total = sum(score * weight for score, weight, _ in grades) -total_weight = sum(weight for _, weight, _ in grades) -average_grade = total / total_weight -print(average_grade) - - -# Example 11 -import collections -Grade = collections.namedtuple('Grade', ('score', 'weight')) - - -# Example 12 -class Subject(object): - def __init__(self): - self._grades = [] - - def report_grade(self, score, weight): - self._grades.append(Grade(score, weight)) - - def average_grade(self): - total, total_weight = 0, 0 - for grade in self._grades: - total += grade.score * grade.weight - total_weight += grade.weight - return total / total_weight - - -# Example 13 -class Student(object): - def __init__(self): - self._subjects = {} - - def subject(self, name): - if name not in self._subjects: - self._subjects[name] = Subject() - return self._subjects[name] - - def average_grade(self): - total, count = 0, 0 - for subject in self._subjects.values(): - total += subject.average_grade() - count += 1 - return total / count - - -# Example 14 -class Gradebook(object): - def __init__(self): - self._students = {} - - def student(self, name): - if name not in self._students: - self._students[name] = Student() - return self._students[name] - - -# Example 15 -book = Gradebook() -albert = book.student('Albert Einstein') -math = albert.subject('Math') -math.report_grade(80, 0.10) -math.report_grade(80, 0.10) -math.report_grade(70, 0.80) -gym = albert.subject('Gym') -gym.report_grade(100, 0.40) -gym.report_grade(85, 0.60) -print(albert.average_grade()) diff --git a/example_code/item_23.py b/example_code/item_23.py deleted file mode 100755 index 45a5c22..0000000 --- a/example_code/item_23.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -names = ['Socrates', 'Archimedes', 'Plato', 'Aristotle'] -names.sort(key=lambda x: len(x)) -print(names) - - -# Example 2 -from collections import defaultdict - -def log_missing(): - print('Key added') - return 0 - - -# Example 3 -current = {'green': 12, 'blue': 3} -increments = [ - ('red', 5), - ('blue', 17), - ('orange', 9), -] -result = defaultdict(log_missing, current) -print('Before:', dict(result)) -for key, amount in increments: - result[key] += amount -print('After: ', dict(result)) - - -# Example 4 -def increment_with_report(current, increments): - added_count = 0 - - def missing(): - nonlocal added_count # Stateful closure - added_count += 1 - return 0 - - result = defaultdict(missing, current) - for key, amount in increments: - result[key] += amount - - return result, added_count - - -# Example 5 -result, count = increment_with_report(current, increments) -assert count == 2 -print(result) - - -# Example 6 -class CountMissing(object): - def __init__(self): - self.added = 0 - - def missing(self): - self.added += 1 - return 0 - - -# Example 7 -counter = CountMissing() -result = defaultdict(counter.missing, current) # Method reference -for key, amount in increments: - result[key] += amount -assert counter.added == 2 -print(result) - - -# Example 8 -class BetterCountMissing(object): - def __init__(self): - self.added = 0 - - def __call__(self): - self.added += 1 - return 0 - -counter = BetterCountMissing() -counter() -assert callable(counter) - - -# Example 9 -counter = BetterCountMissing() -result = defaultdict(counter, current) # Relies on __call__ -for key, amount in increments: - result[key] += amount -assert counter.added == 2 -print(result) diff --git a/example_code/item_24.py b/example_code/item_24.py deleted file mode 100755 index 3d82ee0..0000000 --- a/example_code/item_24.py +++ /dev/null @@ -1,182 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class InputData(object): - def read(self): - raise NotImplementedError - - -# Example 2 -class PathInputData(InputData): - def __init__(self, path): - super().__init__() - self.path = path - - def read(self): - return open(self.path).read() - - -# Example 3 -class Worker(object): - def __init__(self, input_data): - self.input_data = input_data - self.result = None - - def map(self): - raise NotImplementedError - - def reduce(self, other): - raise NotImplementedError - - -# Example 4 -class LineCountWorker(Worker): - def map(self): - data = self.input_data.read() - self.result = data.count('\n') - - def reduce(self, other): - self.result += other.result - - -# Example 5 -import os - -def generate_inputs(data_dir): - for name in os.listdir(data_dir): - yield PathInputData(os.path.join(data_dir, name)) - - -# Example 6 -def create_workers(input_list): - workers = [] - for input_data in input_list: - workers.append(LineCountWorker(input_data)) - return workers - - -# Example 7 -from threading import Thread - -def execute(workers): - threads = [Thread(target=w.map) for w in workers] - for thread in threads: thread.start() - for thread in threads: thread.join() - - first, rest = workers[0], workers[1:] - for worker in rest: - first.reduce(worker) - return first.result - - -# Example 8 -def mapreduce(data_dir): - inputs = generate_inputs(data_dir) - workers = create_workers(inputs) - return execute(workers) - - -# Example 9 -from tempfile import TemporaryDirectory -import random - -def write_test_files(tmpdir): - for i in range(100): - with open(os.path.join(tmpdir, str(i)), 'w') as f: - f.write('\n' * random.randint(0, 100)) - -with TemporaryDirectory() as tmpdir: - write_test_files(tmpdir) - result = mapreduce(tmpdir) - -print('There are', result, 'lines') - - -# Example 10 -class GenericInputData(object): - def read(self): - raise NotImplementedError - - @classmethod - def generate_inputs(cls, config): - raise NotImplementedError - - -# Example 11 -class PathInputData(GenericInputData): - def __init__(self, path): - super().__init__() - self.path = path - - def read(self): - return open(self.path).read() - - @classmethod - def generate_inputs(cls, config): - data_dir = config['data_dir'] - for name in os.listdir(data_dir): - yield cls(os.path.join(data_dir, name)) - - -# Example 12 -class GenericWorker(object): - def __init__(self, input_data): - self.input_data = input_data - self.result = None - - def map(self): - raise NotImplementedError - - def reduce(self, other): - raise NotImplementedError - - @classmethod - def create_workers(cls, input_class, config): - workers = [] - for input_data in input_class.generate_inputs(config): - workers.append(cls(input_data)) - return workers - - -# Example 13 -class LineCountWorker(GenericWorker): - def map(self): - data = self.input_data.read() - self.result = data.count('\n') - - def reduce(self, other): - self.result += other.result - - -# Example 14 -def mapreduce(worker_class, input_class, config): - workers = worker_class.create_workers(input_class, config) - return execute(workers) - - -# Example 15 -with TemporaryDirectory() as tmpdir: - write_test_files(tmpdir) - config = {'data_dir': tmpdir} - result = mapreduce(LineCountWorker, PathInputData, config) -print('There are', result, 'lines') diff --git a/example_code/item_25.py b/example_code/item_25.py deleted file mode 100755 index 7190b68..0000000 --- a/example_code/item_25.py +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class MyBaseClass(object): - def __init__(self, value): - self.value = value - -class MyChildClass(MyBaseClass): - def __init__(self): - MyBaseClass.__init__(self, 5) - - def times_two(self): - return self.value * 2 - -foo = MyChildClass() -print(foo.times_two()) - - -# Example 2 -class TimesTwo(object): - def __init__(self): - self.value *= 2 - -class PlusFive(object): - def __init__(self): - self.value += 5 - - -# Example 3 -class OneWay(MyBaseClass, TimesTwo, PlusFive): - def __init__(self, value): - MyBaseClass.__init__(self, value) - TimesTwo.__init__(self) - PlusFive.__init__(self) - - -# Example 4 -foo = OneWay(5) -print('First ordering is (5 * 2) + 5 =', foo.value) - - -# Example 5 -class AnotherWay(MyBaseClass, PlusFive, TimesTwo): - def __init__(self, value): - MyBaseClass.__init__(self, value) - TimesTwo.__init__(self) - PlusFive.__init__(self) - - -# Example 6 -bar = AnotherWay(5) -print('Second ordering still is', bar.value) - - -# Example 7 -class TimesFive(MyBaseClass): - def __init__(self, value): - MyBaseClass.__init__(self, value) - self.value *= 5 - -class PlusTwo(MyBaseClass): - def __init__(self, value): - MyBaseClass.__init__(self, value) - self.value += 2 - - -# Example 8 -class ThisWay(TimesFive, PlusTwo): - def __init__(self, value): - TimesFive.__init__(self, value) - PlusTwo.__init__(self, value) - -foo = ThisWay(5) -print('Should be (5 * 5) + 2 = 27 but is', foo.value) - - -# Example 11 -# This is pretending to be Python 2 but it's not -class MyBaseClass(object): - def __init__(self, value): - self.value = value - -class TimesFiveCorrect(MyBaseClass): - def __init__(self, value): - super(TimesFiveCorrect, self).__init__(value) - self.value *= 5 - -class PlusTwoCorrect(MyBaseClass): - def __init__(self, value): - super(PlusTwoCorrect, self).__init__(value) - self.value += 2 - -class GoodWay(TimesFiveCorrect, PlusTwoCorrect): - def __init__(self, value): - super(GoodWay, self).__init__(value) - -before_pprint = pprint -pprint(GoodWay.mro()) -from pprint import pprint -pprint(GoodWay.mro()) -pprint = pprint - - -# Example 12 -class Explicit(MyBaseClass): - def __init__(self, value): - super(__class__, self).__init__(value * 2) - -class Implicit(MyBaseClass): - def __init__(self, value): - super().__init__(value * 2) - -assert Explicit(10).value == Implicit(10).value diff --git a/example_code/item_25_example_09.py b/example_code/item_25_example_09.py deleted file mode 100755 index 1955956..0000000 --- a/example_code/item_25_example_09.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 9 -class MyBaseClass(object): - def __init__(self, value): - self.value = value - -class TimesFiveCorrect(MyBaseClass): - def __init__(self, value): - super(TimesFiveCorrect, self).__init__(value) - self.value *= 5 - -class PlusTwoCorrect(MyBaseClass): - def __init__(self, value): - super(PlusTwoCorrect, self).__init__(value) - self.value += 2 diff --git a/example_code/item_25_example_10.py b/example_code/item_25_example_10.py deleted file mode 100755 index b632b11..0000000 --- a/example_code/item_25_example_10.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 10 -class MyBaseClass(object): - def __init__(self, value): - self.value = value - -class TimesFiveCorrect(MyBaseClass): - def __init__(self, value): - super(TimesFiveCorrect, self).__init__(value) - self.value *= 5 - -class PlusTwoCorrect(MyBaseClass): - def __init__(self, value): - super(PlusTwoCorrect, self).__init__(value) - self.value += 2 - -class GoodWay(TimesFiveCorrect, PlusTwoCorrect): - def __init__(self, value): - super(GoodWay, self).__init__(value) - -foo = GoodWay(5) -print 'Should be 5 * (5 + 2) = 35 and is', foo.value diff --git a/example_code/item_26.py b/example_code/item_26.py deleted file mode 100755 index 77f83f5..0000000 --- a/example_code/item_26.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class ToDictMixin(object): - def to_dict(self): - return self._traverse_dict(self.__dict__) - - -# Example 2 - def _traverse_dict(self, instance_dict): - output = {} - for key, value in instance_dict.items(): - output[key] = self._traverse(key, value) - return output - - def _traverse(self, key, value): - if isinstance(value, ToDictMixin): - return value.to_dict() - elif isinstance(value, dict): - return self._traverse_dict(value) - elif isinstance(value, list): - return [self._traverse(key, i) for i in value] - elif hasattr(value, '__dict__'): - return self._traverse_dict(value.__dict__) - else: - return value - - -# Example 3 -class BinaryTree(ToDictMixin): - def __init__(self, value, left=None, right=None): - self.value = value - self.left = left - self.right = right - - -# Example 4 -tree = BinaryTree(10, - left=BinaryTree(7, right=BinaryTree(9)), - right=BinaryTree(13, left=BinaryTree(11))) -orig_print = print -print = pprint -print(tree.to_dict()) -print = orig_print - - -# Example 5 -class BinaryTreeWithParent(BinaryTree): - def __init__(self, value, left=None, - right=None, parent=None): - super().__init__(value, left=left, right=right) - self.parent = parent - - -# Example 6 - def _traverse(self, key, value): - if (isinstance(value, BinaryTreeWithParent) and - key == 'parent'): - return value.value # Prevent cycles - else: - return super()._traverse(key, value) - - -# Example 7 -root = BinaryTreeWithParent(10) -root.left = BinaryTreeWithParent(7, parent=root) -root.left.right = BinaryTreeWithParent(9, parent=root.left) -orig_print = print -print = pprint -print(root.to_dict()) -print = orig_print - - -# Example 8 -class NamedSubTree(ToDictMixin): - def __init__(self, name, tree_with_parent): - self.name = name - self.tree_with_parent = tree_with_parent - -my_tree = NamedSubTree('foobar', root.left.right) -orig_print = print -print = pprint -print(my_tree.to_dict()) # No infinite loop -print = orig_print - - -# Example 9 -import json - -class JsonMixin(object): - @classmethod - def from_json(cls, data): - kwargs = json.loads(data) - return cls(**kwargs) - - def to_json(self): - return json.dumps(self.to_dict()) - - -# Example 10 -class DatacenterRack(ToDictMixin, JsonMixin): - def __init__(self, switch=None, machines=None): - self.switch = Switch(**switch) - self.machines = [ - Machine(**kwargs) for kwargs in machines] - -class Switch(ToDictMixin, JsonMixin): - def __init__(self, ports=None, speed=None): - self.ports = ports - self.speed = speed - -class Machine(ToDictMixin, JsonMixin): - def __init__(self, cores=None, ram=None, disk=None): - self.cores = cores - self.ram = ram - self.disk = disk - - -# Example 11 -serialized = """{ - "switch": {"ports": 5, "speed": 1e9}, - "machines": [ - {"cores": 8, "ram": 32e9, "disk": 5e12}, - {"cores": 4, "ram": 16e9, "disk": 1e12}, - {"cores": 2, "ram": 4e9, "disk": 500e9} - ] -}""" - -deserialized = DatacenterRack.from_json(serialized) -roundtrip = deserialized.to_json() -assert json.loads(serialized) == json.loads(roundtrip) diff --git a/example_code/item_27.py b/example_code/item_27.py deleted file mode 100755 index 512434d..0000000 --- a/example_code/item_27.py +++ /dev/null @@ -1,188 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class MyObject(object): - def __init__(self): - self.public_field = 5 - self.__private_field = 10 - - def get_private_field(self): - return self.__private_field - - -# Example 2 -foo = MyObject() -assert foo.public_field == 5 - - -# Example 3 -assert foo.get_private_field() == 10 - - -# Example 4 -try: - foo.__private_field -except: - logging.exception('Expected') -else: - assert False - - -# Example 5 -class MyOtherObject(object): - def __init__(self): - self.__private_field = 71 - - @classmethod - def get_private_field_of_instance(cls, instance): - return instance.__private_field - -bar = MyOtherObject() -assert MyOtherObject.get_private_field_of_instance(bar) == 71 - - -# Example 6 -try: - class MyParentObject(object): - def __init__(self): - self.__private_field = 71 - - class MyChildObject(MyParentObject): - def get_private_field(self): - return self.__private_field - - baz = MyChildObject() - baz.get_private_field() -except: - logging.exception('Expected') -else: - assert False - - -# Example 7 -assert baz._MyParentObject__private_field == 71 - - -# Example 8 -print(baz.__dict__) - - -# Example 9 -class MyClass(object): - def __init__(self, value): - self.__value = value - - def get_value(self): - return str(self.__value) - -foo = MyClass(5) -assert foo.get_value() == '5' - - -# Example 10 -class MyIntegerSubclass(MyClass): - def get_value(self): - return int(self._MyClass__value) - -foo = MyIntegerSubclass(5) -assert foo.get_value() == 5 - - -# Example 11 -class MyBaseClass(object): - def __init__(self, value): - self.__value = value - - def get_value(self): - return self.__value - -class MyClass(MyBaseClass): - def get_value(self): - return str(super().get_value()) - -class MyIntegerSubclass(MyClass): - def get_value(self): - return int(self._MyClass__value) - - -# Example 12 -try: - foo = MyIntegerSubclass(5) - foo.get_value() -except: - logging.exception('Expected') -else: - assert False - - -# Example 13 -class MyClass(object): - def __init__(self, value): - # This stores the user-supplied value for the object. - # It should be coercible to a string. Once assigned for - # the object it should be treated as immutable. - self._value = value - - def get_value(self): - return str(self._value) - -class MyIntegerSubclass(MyClass): - def get_value(self): - return self._value - -foo = MyIntegerSubclass(5) -assert foo.get_value() == 5 - - -# Example 14 -class ApiClass(object): - def __init__(self): - self._value = 5 - - def get(self): - return self._value - -class Child(ApiClass): - def __init__(self): - super().__init__() - self._value = 'hello' # Conflicts - -a = Child() -print(a.get(), 'and', a._value, 'should be different') - - -# Example 15 -class ApiClass(object): - def __init__(self): - self.__value = 5 - - def get(self): - return self.__value - -class Child(ApiClass): - def __init__(self): - super().__init__() - self._value = 'hello' # OK! - -a = Child() -print(a.get(), 'and', a._value, 'are different') diff --git a/example_code/item_28.py b/example_code/item_28.py deleted file mode 100755 index d09458a..0000000 --- a/example_code/item_28.py +++ /dev/null @@ -1,165 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class FrequencyList(list): - def __init__(self, members): - super().__init__(members) - - def frequency(self): - counts = {} - for item in self: - counts.setdefault(item, 0) - counts[item] += 1 - return counts - - -# Example 2 -foo = FrequencyList(['a', 'b', 'a', 'c', 'b', 'a', 'd']) -print('Length is', len(foo)) -foo.pop() -print('After pop:', repr(foo)) -print('Frequency:', foo.frequency()) - - -# Example 3 -class BinaryNode(object): - def __init__(self, value, left=None, right=None): - self.value = value - self.left = left - self.right = right - - -# Example 4 -bar = [1, 2, 3] -bar[0] - - -# Example 5 -bar.__getitem__(0) - - -# Example 6 -class IndexableNode(BinaryNode): - def _search(self, count, index): - found = None - if self.left: - found, count = self.left._search(count, index) - if not found and count == index: - found = self - else: - count += 1 - if not found and self.right: - found, count = self.right._search(count, index) - return found, count - # Returns (found, count) - - def __getitem__(self, index): - found, _ = self._search(0, index) - if not found: - raise IndexError('Index out of range') - return found.value - - -# Example 7 -tree = IndexableNode( - 10, - left=IndexableNode( - 5, - left=IndexableNode(2), - right=IndexableNode( - 6, right=IndexableNode(7))), - right=IndexableNode( - 15, left=IndexableNode(11))) - - -# Example 8 -print('LRR =', tree.left.right.right.value) -print('Index 0 =', tree[0]) -print('Index 1 =', tree[1]) -print('11 in the tree?', 11 in tree) -print('17 in the tree?', 17 in tree) -print('Tree is', list(tree)) - - -# Example 9 -try: - len(tree) -except: - logging.exception('Expected') -else: - assert False - - -# Example 10 -class SequenceNode(IndexableNode): - def __len__(self): - _, count = self._search(0, None) - return count - - -# Example 11 -tree = SequenceNode( - 10, - left=SequenceNode( - 5, - left=SequenceNode(2), - right=SequenceNode( - 6, right=SequenceNode(7))), - right=SequenceNode( - 15, left=SequenceNode(11)) -) - -print('Tree has %d nodes' % len(tree)) - - -# Example 12 -try: - from collections.abc import Sequence - - class BadType(Sequence): - pass - - foo = BadType() -except: - logging.exception('Expected') -else: - assert False - - -# Example 13 -class BetterNode(SequenceNode, Sequence): - pass - -tree = BetterNode( - 10, - left=BetterNode( - 5, - left=BetterNode(2), - right=BetterNode( - 6, right=BetterNode(7))), - right=BetterNode( - 15, left=BetterNode(11)) -) - -print('Index of 7 is', tree.index(7)) -print('Count of 10 is', tree.count(10)) diff --git a/example_code/item_29.py b/example_code/item_29.py deleted file mode 100755 index cade8bd..0000000 --- a/example_code/item_29.py +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class OldResistor(object): - def __init__(self, ohms): - self._ohms = ohms - - def get_ohms(self): - return self._ohms - - def set_ohms(self, ohms): - self._ohms = ohms - - -# Example 2 -r0 = OldResistor(50e3) -print('Before: %5r' % r0.get_ohms()) -r0.set_ohms(10e3) -print('After: %5r' % r0.get_ohms()) - - -# Example 3 -r0.set_ohms(r0.get_ohms() + 5e3) - - -# Example 4 -class Resistor(object): - def __init__(self, ohms): - self.ohms = ohms - self.voltage = 0 - self.current = 0 - -r1 = Resistor(50e3) -r1.ohms = 10e3 -print('%r ohms, %r volts, %r amps' % - (r1.ohms, r1.voltage, r1.current)) - - -# Example 5 -r1.ohms += 5e3 - - -# Example 6 -class VoltageResistance(Resistor): - def __init__(self, ohms): - super().__init__(ohms) - self._voltage = 0 - - @property - def voltage(self): - return self._voltage - - @voltage.setter - def voltage(self, voltage): - self._voltage = voltage - self.current = self._voltage / self.ohms - - -# Example 7 -r2 = VoltageResistance(1e3) -print('Before: %5r amps' % r2.current) -r2.voltage = 10 -print('After: %5r amps' % r2.current) - - -# Example 8 -class BoundedResistance(Resistor): - def __init__(self, ohms): - super().__init__(ohms) - - @property - def ohms(self): - return self._ohms - - @ohms.setter - def ohms(self, ohms): - if ohms <= 0: - raise ValueError('%f ohms must be > 0' % ohms) - self._ohms = ohms - - -# Example 9 -try: - r3 = BoundedResistance(1e3) - r3.ohms = 0 -except: - logging.exception('Expected') -else: - assert False - - -# Example 10 -try: - BoundedResistance(-5) -except: - logging.exception('Expected') -else: - assert False - - -# Example 11 -class FixedResistance(Resistor): - def __init__(self, ohms): - super().__init__(ohms) - - @property - def ohms(self): - return self._ohms - - @ohms.setter - def ohms(self, ohms): - if hasattr(self, '_ohms'): - raise AttributeError("Can't set attribute") - self._ohms = ohms - - -# Example 12 -try: - r4 = FixedResistance(1e3) - r4.ohms = 2e3 -except: - logging.exception('Expected') -else: - assert False - - -# Example 13 -class MysteriousResistor(Resistor): - @property - def ohms(self): - self.voltage = self._ohms * self.current - return self._ohms - - @ohms.setter - def ohms(self, ohms): - self._ohms = ohms - - -# Example 14 -r7 = MysteriousResistor(10) -r7.current = 0.01 -print('Before: %5r' % r7.voltage) -r7.ohms -print('After: %5r' % r7.voltage) diff --git a/example_code/item_30.py b/example_code/item_30.py deleted file mode 100755 index 47c898c..0000000 --- a/example_code/item_30.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -from datetime import datetime, timedelta - -class Bucket(object): - def __init__(self, period): - self.period_delta = timedelta(seconds=period) - self.reset_time = datetime.now() - self.quota = 0 - - def __repr__(self): - return 'Bucket(quota=%d)' % self.quota - -bucket = Bucket(60) -print(bucket) - - -# Example 2 -def fill(bucket, amount): - now = datetime.now() - if now - bucket.reset_time > bucket.period_delta: - bucket.quota = 0 - bucket.reset_time = now - bucket.quota += amount - - -# Example 3 -def deduct(bucket, amount): - now = datetime.now() - if now - bucket.reset_time > bucket.period_delta: - return False - if bucket.quota - amount < 0: - return False - bucket.quota -= amount - return True - - -# Example 4 -bucket = Bucket(60) -fill(bucket, 100) -print(bucket) - - -# Example 5 -if deduct(bucket, 99): - print('Had 99 quota') -else: - print('Not enough for 99 quota') -print(bucket) - - -# Example 6 -if deduct(bucket, 3): - print('Had 3 quota') -else: - print('Not enough for 3 quota') -print(bucket) - - -# Example 7 -class Bucket(object): - def __init__(self, period): - self.period_delta = timedelta(seconds=period) - self.reset_time = datetime.now() - self.max_quota = 0 - self.quota_consumed = 0 - - def __repr__(self): - return ('Bucket(max_quota=%d, quota_consumed=%d)' % - (self.max_quota, self.quota_consumed)) - - -# Example 8 - @property - def quota(self): - return self.max_quota - self.quota_consumed - - -# Example 9 - @quota.setter - def quota(self, amount): - delta = self.max_quota - amount - if amount == 0: - # Quota being reset for a new period - self.quota_consumed = 0 - self.max_quota = 0 - elif delta < 0: - # Quota being filled for the new period - assert self.quota_consumed == 0 - self.max_quota = amount - else: - # Quota being consumed during the period - assert self.max_quota >= self.quota_consumed - self.quota_consumed += delta - - -# Example 10 -bucket = Bucket(60) -print('Initial', bucket) -fill(bucket, 100) -print('Filled', bucket) - -if deduct(bucket, 99): - print('Had 99 quota') -else: - print('Not enough for 99 quota') - -print('Now', bucket) - -if deduct(bucket, 3): - print('Had 3 quota') -else: - print('Not enough for 3 quota') - -print('Still', bucket) diff --git a/example_code/item_31.py b/example_code/item_31.py deleted file mode 100755 index aa9c6de..0000000 --- a/example_code/item_31.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class Homework(object): - def __init__(self): - self._grade = 0 - - @property - def grade(self): - return self._grade - - @grade.setter - def grade(self, value): - if not (0 <= value <= 100): - raise ValueError('Grade must be between 0 and 100') - self._grade = value - - -# Example 2 -galileo = Homework() -galileo.grade = 95 -print(galileo.grade) - - -# Example 3 -class Exam(object): - def __init__(self): - self._writing_grade = 0 - self._math_grade = 0 - - @staticmethod - def _check_grade(value): - if not (0 <= value <= 100): - raise ValueError('Grade must be between 0 and 100') - - -# Example 4 - @property - def writing_grade(self): - return self._writing_grade - - @writing_grade.setter - def writing_grade(self, value): - self._check_grade(value) - self._writing_grade = value - - @property - def math_grade(self): - return self._math_grade - - @math_grade.setter - def math_grade(self, value): - self._check_grade(value) - self._math_grade = value - - -# Example 5 -galileo = Exam() -galileo.writing_grade = 85 -galileo.math_grade = 99 -print('Writing: %5r' % galileo.writing_grade) -print('Math: %5r' % galileo.math_grade) - - -# Example 6 -class Grade(object): - def __get__(*args, **kwargs): - pass - - def __set__(*args, **kwargs): - pass - -class Exam(object): - # Class attributes - math_grade = Grade() - writing_grade = Grade() - science_grade = Grade() - - -# Example 7 -exam = Exam() -exam.writing_grade = 40 - - -# Example 8 -Exam.__dict__['writing_grade'].__set__(exam, 40) - - -# Example 9 -print(exam.writing_grade) - - -# Example 10 -print(Exam.__dict__['writing_grade'].__get__(exam, Exam)) - - -# Example 11 -class Grade(object): - def __init__(self): - self._value = 0 - - def __get__(self, instance, instance_type): - return self._value - - def __set__(self, instance, value): - if not (0 <= value <= 100): - raise ValueError('Grade must be between 0 and 100') - self._value = value - -class Exam(object): - math_grade = Grade() - writing_grade = Grade() - science_grade = Grade() - - -# Example 12 -first_exam = Exam() -first_exam.writing_grade = 82 -first_exam.science_grade = 99 -print('Writing', first_exam.writing_grade) -print('Science', first_exam.science_grade) - - -# Example 13 -second_exam = Exam() -second_exam.writing_grade = 75 -print('Second', second_exam.writing_grade, 'is right') -print('First ', first_exam.writing_grade, 'is wrong') - - -# Example 14 -class Grade(object): - def __init__(self): - self._values = {} - - def __get__(self, instance, instance_type): - if instance is None: return self - return self._values.get(instance, 0) - - def __set__(self, instance, value): - if not (0 <= value <= 100): - raise ValueError('Grade must be between 0 and 100') - self._values[instance] = value - - -# Example 15 -from weakref import WeakKeyDictionary - -class Grade(object): - def __init__(self): - self._values = WeakKeyDictionary() - def __get__(self, instance, instance_type): - if instance is None: return self - return self._values.get(instance, 0) - - def __set__(self, instance, value): - if not (0 <= value <= 100): - raise ValueError('Grade must be between 0 and 100') - self._values[instance] = value - - -# Example 16 -class Exam(object): - math_grade = Grade() - writing_grade = Grade() - science_grade = Grade() - -first_exam = Exam() -first_exam.writing_grade = 82 -second_exam = Exam() -second_exam.writing_grade = 75 -print('First ', first_exam.writing_grade, 'is right') -print('Second', second_exam.writing_grade, 'is right') diff --git a/example_code/item_32.py b/example_code/item_32.py deleted file mode 100755 index 49837d3..0000000 --- a/example_code/item_32.py +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class LazyDB(object): - def __init__(self): - self.exists = 5 - - def __getattr__(self, name): - value = 'Value for %s' % name - setattr(self, name, value) - return value - - -# Example 2 -data = LazyDB() -print('Before:', data.__dict__) -print('foo: ', data.foo) -print('After: ', data.__dict__) - - -# Example 3 -class LoggingLazyDB(LazyDB): - def __getattr__(self, name): - print('Called __getattr__(%s)' % name) - return super().__getattr__(name) - -data = LoggingLazyDB() -print('exists:', data.exists) -print('foo: ', data.foo) -print('foo: ', data.foo) - - -# Example 4 -class ValidatingDB(object): - def __init__(self): - self.exists = 5 - - def __getattribute__(self, name): - print('Called __getattribute__(%s)' % name) - try: - return super().__getattribute__(name) - except AttributeError: - value = 'Value for %s' % name - setattr(self, name, value) - return value - -data = ValidatingDB() -print('exists:', data.exists) -print('foo: ', data.foo) -print('foo: ', data.foo) - - -# Example 5 -try: - class MissingPropertyDB(object): - def __getattr__(self, name): - if name == 'bad_name': - raise AttributeError('%s is missing' % name) - value = 'Value for %s' % name - setattr(self, name, value) - return value - - data = MissingPropertyDB() - data.foo # Test this works - data.bad_name -except: - logging.exception('Expected') -else: - assert False - - -# Example 6 -data = LoggingLazyDB() -print('Before: ', data.__dict__) -print('foo exists: ', hasattr(data, 'foo')) -print('After: ', data.__dict__) -print('foo exists: ', hasattr(data, 'foo')) - - -# Example 7 -data = ValidatingDB() -print('foo exists: ', hasattr(data, 'foo')) -print('foo exists: ', hasattr(data, 'foo')) - - -# Example 8 -class SavingDB(object): - def __setattr__(self, name, value): - # Save some data to the DB log - super().__setattr__(name, value) - - -# Example 9 -class LoggingSavingDB(SavingDB): - def __setattr__(self, name, value): - print('Called __setattr__(%s, %r)' % (name, value)) - super().__setattr__(name, value) - -data = LoggingSavingDB() -print('Before: ', data.__dict__) -data.foo = 5 -print('After: ', data.__dict__) -data.foo = 7 -print('Finally:', data.__dict__) - - -# Example 10 -class BrokenDictionaryDB(object): - def __init__(self, data): - self._data = data - - def __getattribute__(self, name): - print('Called __getattribute__(%s)' % name) - return self._data[name] - - -# Example 11 -try: - data = BrokenDictionaryDB({'foo': 3}) - data.foo -except: - logging.exception('Expected') -else: - assert False - - -# Example 12 -class DictionaryDB(object): - def __init__(self, data): - self._data = data - - def __getattribute__(self, name): - data_dict = super().__getattribute__('_data') - return data_dict[name] - -data = DictionaryDB({'foo': 3}) -print(data.foo) diff --git a/example_code/item_33.py b/example_code/item_33.py deleted file mode 100755 index 90293a2..0000000 --- a/example_code/item_33.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class Meta(type): - def __new__(meta, name, bases, class_dict): - orig_print = __builtins__.print - print = pprint - print((meta, name, bases, class_dict)) - print = orig_print - return type.__new__(meta, name, bases, class_dict) - -class MyClass(object, metaclass=Meta): - stuff = 123 - - def foo(self): - pass - - -# Example 3 -class ValidatePolygon(type): - def __new__(meta, name, bases, class_dict): - # Don't validate the abstract Polygon class - if bases != (object,): - if class_dict['sides'] < 3: - raise ValueError('Polygons need 3+ sides') - return type.__new__(meta, name, bases, class_dict) - -class Polygon(object, metaclass=ValidatePolygon): - sides = None # Specified by subclasses - - @classmethod - def interior_angles(cls): - return (cls.sides - 2) * 180 - -class Triangle(Polygon): - sides = 3 - -print(Triangle.interior_angles()) - - -# Example 4 -try: - print('Before class') - class Line(Polygon): - print('Before sides') - sides = 1 - print('After sides') - print('After class') -except: - logging.exception('Expected') -else: - assert False diff --git a/example_code/item_33_example_02.py b/example_code/item_33_example_02.py deleted file mode 100755 index 1ff6adf..0000000 --- a/example_code/item_33_example_02.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 2 -class Meta(type): - def __new__(meta, name, bases, class_dict): - print(meta, name, bases, class_dict) - return type.__new__(meta, name, bases, class_dict) - -class MyClassInPython2(object): - __metaclass__ = Meta - stuff = 123 - - def foo(self): - pass diff --git a/example_code/item_34.py b/example_code/item_34.py deleted file mode 100755 index 792c69d..0000000 --- a/example_code/item_34.py +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -import json - -class Serializable(object): - def __init__(self, *args): - self.args = args - - def serialize(self): - return json.dumps({'args': self.args}) - - -# Example 2 -class Point2D(Serializable): - def __init__(self, x, y): - super().__init__(x, y) - self.x = x - self.y = y - - def __repr__(self): - return 'Point2D(%d, %d)' % (self.x, self.y) - -point = Point2D(5, 3) -print('Object: ', point) -print('Serialized:', point.serialize()) - - -# Example 3 -class Deserializable(Serializable): - @classmethod - def deserialize(cls, json_data): - params = json.loads(json_data) - return cls(*params['args']) - - -# Example 4 -class BetterPoint2D(Deserializable): - def __init__(self, x, y): - super().__init__(x, y) - self.x = x - self.y = y - - def __repr__(self): - return 'BetterPoint2D(%d, %d)' % (self.x, self.y) - -point = BetterPoint2D(5, 3) -print('Before: ', point) -data = point.serialize() -print('Serialized:', data) -after = BetterPoint2D.deserialize(data) -print('After: ', after) - - -# Example 5 -class BetterSerializable(object): - def __init__(self, *args): - self.args = args - - def serialize(self): - return json.dumps({ - 'class': self.__class__.__name__, - 'args': self.args, - }) - - def __repr__(self): - return '%s(%s)' % ( - self.__class__.__name__, - ', '.join(str(x) for x in self.args)) - - -# Example 6 -registry = {} - -def register_class(target_class): - registry[target_class.__name__] = target_class - -def deserialize(data): - params = json.loads(data) - name = params['class'] - target_class = registry[name] - return target_class(*params['args']) - - -# Example 7 -class EvenBetterPoint2D(BetterSerializable): - def __init__(self, x, y): - super().__init__(x, y) - self.x = x - self.y = y - -register_class(EvenBetterPoint2D) - - -# Example 8 -point = EvenBetterPoint2D(5, 3) -print('Before: ', point) -data = point.serialize() -print('Serialized:', data) -after = deserialize(data) -print('After: ', after) - - -# Example 9 -class Point3D(BetterSerializable): - def __init__(self, x, y, z): - super().__init__(x, y, z) - self.x = x - self.y = y - self.z = z - -# Forgot to call register_class! Whoops! - - -# Example 10 -try: - point = Point3D(5, 9, -4) - data = point.serialize() - deserialize(data) -except: - logging.exception('Expected') -else: - assert False - - -# Example 11 -class Meta(type): - def __new__(meta, name, bases, class_dict): - cls = type.__new__(meta, name, bases, class_dict) - register_class(cls) - return cls - -class RegisteredSerializable(BetterSerializable, metaclass=Meta): - pass - - -# Example 12 -class Vector3D(RegisteredSerializable): - def __init__(self, x, y, z): - super().__init__(x, y, z) - self.x, self.y, self.z = x, y, z - -v3 = Vector3D(10, -7, 3) -print('Before: ', v3) -data = v3.serialize() -print('Serialized:', data) -print('After: ', deserialize(data)) diff --git a/example_code/item_35.py b/example_code/item_35.py deleted file mode 100755 index 5553d20..0000000 --- a/example_code/item_35.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class Field(object): - def __init__(self, name): - self.name = name - self.internal_name = '_' + self.name - - def __get__(self, instance, instance_type): - if instance is None: return self - return getattr(instance, self.internal_name, '') - - def __set__(self, instance, value): - setattr(instance, self.internal_name, value) - - -# Example 2 -class Customer(object): - # Class attributes - first_name = Field('first_name') - last_name = Field('last_name') - prefix = Field('prefix') - suffix = Field('suffix') - - -# Example 3 -foo = Customer() -print('Before:', repr(foo.first_name), foo.__dict__) -foo.first_name = 'Euclid' -print('After: ', repr(foo.first_name), foo.__dict__) - - -# Example 4 -class Meta(type): - def __new__(meta, name, bases, class_dict): - for key, value in class_dict.items(): - if isinstance(value, Field): - value.name = key - value.internal_name = '_' + key - cls = type.__new__(meta, name, bases, class_dict) - return cls - - -# Example 5 -class DatabaseRow(object, metaclass=Meta): - pass - - -# Example 6 -class Field(object): - def __init__(self): - # These will be assigned by the metaclass. - self.name = None - self.internal_name = None - def __get__(self, instance, instance_type): - if instance is None: return self - return getattr(instance, self.internal_name, '') - - def __set__(self, instance, value): - setattr(instance, self.internal_name, value) - - -# Example 7 -class BetterCustomer(DatabaseRow): - first_name = Field() - last_name = Field() - prefix = Field() - suffix = Field() - - -# Example 8 -foo = BetterCustomer() -print('Before:', repr(foo.first_name), foo.__dict__) -foo.first_name = 'Euler' -print('After: ', repr(foo.first_name), foo.__dict__) diff --git a/example_code/item_36.py b/example_code/item_36.py deleted file mode 100755 index 892c416..0000000 --- a/example_code/item_36.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -import subprocess -proc = subprocess.Popen( - ['echo', 'Hello from the child!'], - stdout=subprocess.PIPE) -out, err = proc.communicate() -print(out.decode('utf-8')) - - -# Example 2 -from time import sleep, time -proc = subprocess.Popen(['sleep', '0.3']) -while proc.poll() is None: - print('Working...') - # Some time consuming work here - sleep(0.2) - -print('Exit status', proc.poll()) - - -# Example 3 -def run_sleep(period): - proc = subprocess.Popen(['sleep', str(period)]) - return proc - -start = time() -procs = [] -for _ in range(10): - proc = run_sleep(0.1) - procs.append(proc) - - -# Example 4 -for proc in procs: - proc.communicate() -end = time() -print('Finished in %.3f seconds' % (end - start)) - - -# Example 5 -import os - -def run_openssl(data): - env = os.environ.copy() - env['password'] = b'\xe24U\n\xd0Ql3S\x11' - proc = subprocess.Popen( - ['openssl', 'enc', '-des3', '-pass', 'env:password'], - env=env, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE) - proc.stdin.write(data) - proc.stdin.flush() # Ensure the child gets input - return proc - - -# Example 6 -import os -procs = [] -for _ in range(3): - data = os.urandom(10) - proc = run_openssl(data) - procs.append(proc) - - -# Example 7 -for proc in procs: - out, err = proc.communicate() - print(out[-10:]) - - -# Example 8 -def run_md5(input_stdin): - proc = subprocess.Popen( - ['md5'], - stdin=input_stdin, - stdout=subprocess.PIPE) - return proc - - -# Example 9 -input_procs = [] -hash_procs = [] -for _ in range(3): - data = os.urandom(10) - proc = run_openssl(data) - input_procs.append(proc) - hash_proc = run_md5(proc.stdout) - hash_procs.append(hash_proc) - - -# Example 10 -for proc in input_procs: - proc.communicate() -for proc in hash_procs: - out, err = proc.communicate() - print(out.strip()) - - -# Example 11 -proc = run_sleep(10) -try: - proc.communicate(timeout=0.1) -except subprocess.TimeoutExpired: - proc.terminate() - proc.wait() - -print('Exit status', proc.poll()) diff --git a/example_code/item_37.py b/example_code/item_37.py deleted file mode 100755 index 57d1286..0000000 --- a/example_code/item_37.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def factorize(number): - for i in range(1, number + 1): - if number % i == 0: - yield i - - -# Example 2 -from time import time -numbers = [2139079, 1214759, 1516637, 1852285] -start = time() -for number in numbers: - list(factorize(number)) -end = time() -print('Took %.3f seconds' % (end - start)) - - -# Example 3 -from threading import Thread - -class FactorizeThread(Thread): - def __init__(self, number): - super().__init__() - self.number = number - - def run(self): - self.factors = list(factorize(self.number)) - - -# Example 4 -start = time() -threads = [] -for number in numbers: - thread = FactorizeThread(number) - thread.start() - threads.append(thread) - - -# Example 5 -for thread in threads: - thread.join() -end = time() -print('Took %.3f seconds' % (end - start)) - - -# Example 6 -import select, socket - -# Creating the socket is specifically to support Windows. Windows can't do -# a select call with an empty list. -def slow_systemcall(): - select.select([socket.socket()], [], [], 0.1) - - -# Example 7 -start = time() -for _ in range(5): - slow_systemcall() -end = time() -print('Took %.3f seconds' % (end - start)) - - -# Example 8 -start = time() -threads = [] -for _ in range(5): - thread = Thread(target=slow_systemcall) - thread.start() - threads.append(thread) - - -# Example 9 -def compute_helicopter_location(index): - pass - -for i in range(5): - compute_helicopter_location(i) -for thread in threads: - thread.join() -end = time() -print('Took %.3f seconds' % (end - start)) diff --git a/example_code/item_38.py b/example_code/item_38.py deleted file mode 100755 index e781d51..0000000 --- a/example_code/item_38.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class Counter(object): - def __init__(self): - self.count = 0 - - def increment(self, offset): - self.count += offset - - -# Example 2 -def worker(sensor_index, how_many, counter): - # I have a barrier in here so the workers synchronize - # when they start counting, otherwise it's hard to get a race - # because the overhead of starting a thread is high. - BARRIER.wait() - for _ in range(how_many): - # Read from the sensor - counter.increment(1) - - -# Example 3 -from threading import Barrier, Thread -BARRIER = Barrier(5) -def run_threads(func, how_many, counter): - threads = [] - for i in range(5): - args = (i, how_many, counter) - thread = Thread(target=func, args=args) - threads.append(thread) - thread.start() - for thread in threads: - thread.join() - - -# Example 4 -how_many = 10**5 -counter = Counter() -run_threads(worker, how_many, counter) -print('Counter should be %d, found %d' % - (5 * how_many, counter.count)) - - -# Example 5 -offset = 5 -counter.count += offset - - -# Example 6 -value = getattr(counter, 'count') -result = value + offset -setattr(counter, 'count', result) - - -# Example 7 -# Running in Thread A -value_a = getattr(counter, 'count') -# Context switch to Thread B -value_b = getattr(counter, 'count') -result_b = value_b + 1 -setattr(counter, 'count', result_b) -# Context switch back to Thread A -result_a = value_a + 1 -setattr(counter, 'count', result_a) - - -# Example 8 -from threading import Lock - -class LockingCounter(object): - def __init__(self): - self.lock = Lock() - self.count = 0 - - def increment(self, offset): - with self.lock: - self.count += offset - - -# Example 9 -BARRIER = Barrier(5) -counter = LockingCounter() -run_threads(worker, how_many, counter) -print('Counter should be %d, found %d' % - (5 * how_many, counter.count)) diff --git a/example_code/item_39.py b/example_code/item_39.py deleted file mode 100755 index 3bcd1ac..0000000 --- a/example_code/item_39.py +++ /dev/null @@ -1,248 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def download(item): - return item - -def resize(item): - return item - -def upload(item): - return item - - -# Example 2 -from threading import Lock -from collections import deque - -class MyQueue(object): - def __init__(self): - self.items = deque() - self.lock = Lock() - - -# Example 3 - def put(self, item): - with self.lock: - self.items.append(item) - - -# Example 4 - def get(self): - with self.lock: - return self.items.popleft() - - -# Example 5 -from threading import Thread -from time import sleep - -class Worker(Thread): - def __init__(self, func, in_queue, out_queue): - super().__init__() - self.func = func - self.in_queue = in_queue - self.out_queue = out_queue - self.polled_count = 0 - self.work_done = 0 - - -# Example 6 - def run(self): - while True: - self.polled_count += 1 - try: - item = self.in_queue.get() - except IndexError: - sleep(0.01) # No work to do - except AttributeError: - # The magic exit signal - return - else: - result = self.func(item) - self.out_queue.put(result) - self.work_done += 1 - - -# Example 7 -download_queue = MyQueue() -resize_queue = MyQueue() -upload_queue = MyQueue() -done_queue = MyQueue() -threads = [ - Worker(download, download_queue, resize_queue), - Worker(resize, resize_queue, upload_queue), - Worker(upload, upload_queue, done_queue), -] - - -# Example 8 -for thread in threads: - thread.start() -for _ in range(1000): - download_queue.put(object()) - - -# Example 9 -import time -while len(done_queue.items) < 1000: - # Do something useful while waiting - time.sleep(0.1) -# Stop all the threads by causing an exception in their -# run methods. -for thread in threads: - thread.in_queue = None - - -# Example 10 -processed = len(done_queue.items) -polled = sum(t.polled_count for t in threads) -print('Processed', processed, 'items after polling', - polled, 'times') - - -# Example 11 -from queue import Queue -queue = Queue() - -def consumer(): - print('Consumer waiting') - queue.get() # Runs after put() below - print('Consumer done') - -thread = Thread(target=consumer) -thread.start() - - -# Example 12 -print('Producer putting') -queue.put(object()) # Runs before get() above -thread.join() -print('Producer done') - - -# Example 13 -queue = Queue(1) # Buffer size of 1 - -def consumer(): - time.sleep(0.1) # Wait - queue.get() # Runs second - print('Consumer got 1') - queue.get() # Runs fourth - print('Consumer got 2') - -thread = Thread(target=consumer) -thread.start() - - -# Example 14 -queue.put(object()) # Runs first -print('Producer put 1') -queue.put(object()) # Runs third -print('Producer put 2') -thread.join() -print('Producer done') - - -# Example 15 -in_queue = Queue() - -def consumer(): - print('Consumer waiting') - work = in_queue.get() # Done second - print('Consumer working') - # Doing work - print('Consumer done') - in_queue.task_done() # Done third - -Thread(target=consumer).start() - - -# Example 16 -in_queue.put(object()) # Done first -print('Producer waiting') -in_queue.join() # Done fourth -print('Producer done') - - -# Example 17 -class ClosableQueue(Queue): - SENTINEL = object() - - def close(self): - self.put(self.SENTINEL) - - -# Example 18 - def __iter__(self): - while True: - item = self.get() - try: - if item is self.SENTINEL: - return # Cause the thread to exit - yield item - finally: - self.task_done() - - -# Example 19 -class StoppableWorker(Thread): - def __init__(self, func, in_queue, out_queue): - super().__init__() - self.func = func - self.in_queue = in_queue - self.out_queue = out_queue - - def run(self): - for item in self.in_queue: - result = self.func(item) - self.out_queue.put(result) - - -# Example 20 -download_queue = ClosableQueue() -resize_queue = ClosableQueue() -upload_queue = ClosableQueue() -done_queue = ClosableQueue() -threads = [ - StoppableWorker(download, download_queue, resize_queue), - StoppableWorker(resize, resize_queue, upload_queue), - StoppableWorker(upload, upload_queue, done_queue), -] - - -# Example 21 -for thread in threads: - thread.start() -for _ in range(1000): - download_queue.put(object()) -download_queue.close() - - -# Example 22 -download_queue.join() -resize_queue.close() -resize_queue.join() -upload_queue.close() -upload_queue.join() -print(done_queue.qsize(), 'items finished') diff --git a/example_code/item_40.py b/example_code/item_40.py deleted file mode 100755 index a4c01a5..0000000 --- a/example_code/item_40.py +++ /dev/null @@ -1,255 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def my_coroutine(): - while True: - received = yield - print('Received:', received) - -it = my_coroutine() -next(it) # Prime the coroutine -it.send('First') -it.send('Second') - - -# Example 2 -def minimize(): - current = yield - while True: - value = yield current - current = min(value, current) - - -# Example 3 -it = minimize() -next(it) # Prime the generator -print(it.send(10)) -print(it.send(4)) -print(it.send(22)) -print(it.send(-1)) - - -# Example 4 -ALIVE = '*' -EMPTY = '-' - - -# Example 5 -from collections import namedtuple -Query = namedtuple('Query', ('y', 'x')) - - -# Example 6 -def count_neighbors(y, x): - n_ = yield Query(y + 1, x + 0) # North - ne = yield Query(y + 1, x + 1) # Northeast - # Define e_, se, s_, sw, w_, nw ... - e_ = yield Query(y + 0, x + 1) # East - se = yield Query(y - 1, x + 1) # Southeast - s_ = yield Query(y - 1, x + 0) # South - sw = yield Query(y - 1, x - 1) # Southwest - w_ = yield Query(y + 0, x - 1) # West - nw = yield Query(y + 1, x - 1) # Northwest - neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] - count = 0 - for state in neighbor_states: - if state == ALIVE: - count += 1 - return count - - -# Example 7 -it = count_neighbors(10, 5) -q1 = next(it) # Get the first query -print('First yield: ', q1) -q2 = it.send(ALIVE) # Send q1 state, get q2 -print('Second yield:', q2) -q3 = it.send(ALIVE) # Send q2 state, get q3 -print('...') -q4 = it.send(EMPTY) -q5 = it.send(EMPTY) -q6 = it.send(EMPTY) -q7 = it.send(EMPTY) -q8 = it.send(EMPTY) -try: - it.send(EMPTY) # Send q8 state, retrieve count -except StopIteration as e: - print('Count: ', e.value) # Value from return statement - - -# Example 8 -Transition = namedtuple('Transition', ('y', 'x', 'state')) - - -# Example 9 -def game_logic(state, neighbors): - pass - -def step_cell(y, x): - state = yield Query(y, x) - neighbors = yield from count_neighbors(y, x) - next_state = game_logic(state, neighbors) - yield Transition(y, x, next_state) - - -# Example 10 -def game_logic(state, neighbors): - if state == ALIVE: - if neighbors < 2: - return EMPTY # Die: Too few - elif neighbors > 3: - return EMPTY # Die: Too many - else: - if neighbors == 3: - return ALIVE # Regenerate - return state - - -# Example 11 -it = step_cell(10, 5) -q0 = next(it) # Initial location query -print('Me: ', q0) -q1 = it.send(ALIVE) # Send my status, get neighbor query -print('Q1: ', q1) -print('...') -q2 = it.send(ALIVE) -q3 = it.send(ALIVE) -q4 = it.send(ALIVE) -q5 = it.send(ALIVE) -q6 = it.send(EMPTY) -q7 = it.send(EMPTY) -q8 = it.send(EMPTY) -t1 = it.send(EMPTY) # Send for q8, get game decision -print('Outcome: ', t1) - - -# Example 12 -TICK = object() - -def simulate(height, width): - while True: - for y in range(height): - for x in range(width): - yield from step_cell(y, x) - yield TICK - - -# Example 13 -class Grid(object): - def __init__(self, height, width): - self.height = height - self.width = width - self.rows = [] - for _ in range(self.height): - self.rows.append([EMPTY] * self.width) - - def __str__(self): - output = '' - for row in self.rows: - for cell in row: - output += cell - output += '\n' - return output - - -# Example 14 - def query(self, y, x): - return self.rows[y % self.height][x % self.width] - - def assign(self, y, x, state): - self.rows[y % self.height][x % self.width] = state - - -# Example 15 -def live_a_generation(grid, sim): - progeny = Grid(grid.height, grid.width) - item = next(sim) - while item is not TICK: - if isinstance(item, Query): - state = grid.query(item.y, item.x) - item = sim.send(state) - else: # Must be a Transition - progeny.assign(item.y, item.x, item.state) - item = next(sim) - return progeny - - -# Example 16 -grid = Grid(5, 9) -grid.assign(0, 3, ALIVE) -grid.assign(1, 4, ALIVE) -grid.assign(2, 2, ALIVE) -grid.assign(2, 3, ALIVE) -grid.assign(2, 4, ALIVE) -print(grid) - - -# Example 17 -class ColumnPrinter(object): - def __init__(self): - self.columns = [] - - def append(self, data): - self.columns.append(data) - - def __str__(self): - row_count = 1 - for data in self.columns: - row_count = max(row_count, len(data.splitlines()) + 1) - rows = [''] * row_count - for j in range(row_count): - for i, data in enumerate(self.columns): - line = data.splitlines()[max(0, j - 1)] - if j == 0: - padding = ' ' * (len(line) // 2) - rows[j] += padding + str(i) + padding - else: - rows[j] += line - if (i + 1) < len(self.columns): - rows[j] += ' | ' - return '\n'.join(rows) - -columns = ColumnPrinter() -sim = simulate(grid.height, grid.width) -for i in range(5): - columns.append(str(grid)) - grid = live_a_generation(grid, sim) - -print(columns) - - -# Example 20 -# This is for the introductory diagram -grid = Grid(5, 5) -grid.assign(1, 1, ALIVE) -grid.assign(2, 2, ALIVE) -grid.assign(2, 3, ALIVE) -grid.assign(3, 3, ALIVE) - -columns = ColumnPrinter() -sim = simulate(grid.height, grid.width) -for i in range(5): - columns.append(str(grid)) - grid = live_a_generation(grid, sim) - -print(columns) diff --git a/example_code/item_40_example_18.py b/example_code/item_40_example_18.py deleted file mode 100755 index 2f5c023..0000000 --- a/example_code/item_40_example_18.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 18 -def delegated(): - yield 1 - yield 2 - -def composed(): - yield 'A' - for value in delegated(): # yield from in Python 3 - yield value - yield 'B' - -print list(composed()) diff --git a/example_code/item_40_example_19.py b/example_code/item_40_example_19.py deleted file mode 100755 index 78a754f..0000000 --- a/example_code/item_40_example_19.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 19 -class MyReturn(Exception): - def __init__(self, value): - self.value = value - -def delegated(): - yield 1 - raise MyReturn(2) # return 2 in Python 3 - yield 'Not reached' - -def composed(): - try: - for value in delegated(): - yield value - except MyReturn as e: - output = e.value - yield output * 4 - -print list(composed()) diff --git a/example_code/item_41.py b/example_code/item_41.py deleted file mode 100755 index 5acafde..0000000 --- a/example_code/item_41.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def gcd(pair): - a, b = pair - low = min(a, b) - for i in range(low, 0, -1): - if a % i == 0 and b % i == 0: - return i - - -# Example 2 -from time import time -numbers = [(1963309, 2265973), (2030677, 3814172), - (1551645, 2229620), (2039045, 2020802)] -start = time() -results = list(map(gcd, numbers)) -end = time() -print('Took %.3f seconds' % (end - start)) - - -# Example 3 -from concurrent.futures import ThreadPoolExecutor - -start = time() -pool = ThreadPoolExecutor(max_workers=2) -results = list(pool.map(gcd, numbers)) -end = time() -print('Took %.3f seconds' % (end - start)) - - -# Example 4 -from concurrent.futures import ProcessPoolExecutor - -start = time() -pool = ProcessPoolExecutor(max_workers=2) # The one change -results = list(pool.map(gcd, numbers)) -end = time() -print('Took %.3f seconds' % (end - start)) diff --git a/example_code/item_42.py b/example_code/item_42.py deleted file mode 100755 index 43395bb..0000000 --- a/example_code/item_42.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def trace(func): - def wrapper(*args, **kwargs): - result = func(*args, **kwargs) - print('%s(%r, %r) -> %r' % - (func.__name__, args, kwargs, result)) - return result - return wrapper - - -# Example 2 -@trace -def fibonacci(n): - """Return the n-th Fibonacci number""" - if n in (0, 1): - return n - return (fibonacci(n - 2) + fibonacci(n - 1)) - - -# Example 3 -def fibonacci(n): - """Return the n-th Fibonacci number""" - if n in (0, 1): - return n - return (fibonacci(n - 2) + fibonacci(n - 1)) - -fibonacci = trace(fibonacci) - - -# Example 4 -fibonacci(3) - - -# Example 5 -print(fibonacci) - - -# Example 6 -try: - # Example of how pickle breaks - import pickle - - def my_func(): - return 1 - - # This will be okay - print(pickle.dumps(my_func)) - - @trace - def my_func2(): - return 2 - - # This will explode - print(pickle.dumps(my_func2)) -except: - logging.exception('Expected') -else: - assert False - - -# Example 7 -help(fibonacci) - - -# Example 8 -from functools import wraps -def trace(func): - @wraps(func) - def wrapper(*args, **kwargs): - result = func(*args, **kwargs) - print('%s(%r, %r) -> %r' % - (func.__name__, args, kwargs, result)) - return result - return wrapper - -@trace -def fibonacci(n): - """Return the n-th Fibonacci number""" - if n in (0, 1): - return n - return (fibonacci(n - 2) + - fibonacci(n - 1)) - - -# Example 9 -help(fibonacci) diff --git a/example_code/item_43.py b/example_code/item_43.py deleted file mode 100755 index 7e0410a..0000000 --- a/example_code/item_43.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -from threading import Lock -lock = Lock() -with lock: - print('Lock is held') - - -# Example 2 -lock.acquire() -try: - print('Lock is held') -finally: - lock.release() - - -# Example 3 -import logging -logging.getLogger().setLevel(logging.WARNING) -def my_function(): - logging.debug('Some debug data') - logging.error('Error log here') - logging.debug('More debug data') - - -# Example 4 -my_function() - - -# Example 5 -from contextlib import contextmanager -@contextmanager -def debug_logging(level): - logger = logging.getLogger() - old_level = logger.getEffectiveLevel() - logger.setLevel(level) - try: - yield - finally: - logger.setLevel(old_level) - - -# Example 6 -with debug_logging(logging.DEBUG): - print('Inside:') - my_function() -print('After:') -my_function() - - -# Example 7 -with open('my_output.txt', 'w') as handle: - handle.write('This is some data!') - - -# Example 8 -@contextmanager -def log_level(level, name): - logger = logging.getLogger(name) - old_level = logger.getEffectiveLevel() - logger.setLevel(level) - try: - yield logger - finally: - logger.setLevel(old_level) - - -# Example 9 -with log_level(logging.DEBUG, 'my-log') as logger: - logger.debug('This is my message!') - logging.debug('This will not print') - - -# Example 10 -logger = logging.getLogger('my-log') -logger.debug('Debug will not print') -logger.error('Error will print') diff --git a/example_code/item_44.py b/example_code/item_44.py deleted file mode 100755 index 3910cdc..0000000 --- a/example_code/item_44.py +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class GameState(object): - def __init__(self): - self.level = 0 - self.lives = 4 - - -# Example 2 -state = GameState() -state.level += 1 # Player beat a level -state.lives -= 1 # Player had to try again - - -# Example 3 -import pickle -state_path = 'game_state.bin' -with open(state_path, 'wb') as f: - pickle.dump(state, f) - - -# Example 4 -with open(state_path, 'rb') as f: - state_after = pickle.load(f) -print(state_after.__dict__) - - -# Example 5 -class GameState(object): - def __init__(self): - self.level = 0 - self.lives = 4 - self.points = 0 - - -# Example 6 -state = GameState() -serialized = pickle.dumps(state) -state_after = pickle.loads(serialized) -print(state_after.__dict__) - - -# Example 7 -with open(state_path, 'rb') as f: - state_after = pickle.load(f) -print(state_after.__dict__) - - -# Example 8 -assert isinstance(state_after, GameState) - - -# Example 9 -class GameState(object): - def __init__(self, level=0, lives=4, points=0): - self.level = level - self.lives = lives - self.points = points - - -# Example 10 -def pickle_game_state(game_state): - kwargs = game_state.__dict__ - return unpickle_game_state, (kwargs,) - - -# Example 11 -def unpickle_game_state(kwargs): - return GameState(**kwargs) - - -# Example 12 -import copyreg -copyreg.pickle(GameState, pickle_game_state) - - -# Example 13 -state = GameState() -state.points += 1000 -serialized = pickle.dumps(state) -state_after = pickle.loads(serialized) -print(state_after.__dict__) - - -# Example 14 -class GameState(object): - def __init__(self, level=0, lives=4, points=0, magic=5): - self.level = level - self.lives = lives - self.points = points - self.magic = magic - - -# Example 15 -state_after = pickle.loads(serialized) -print(state_after.__dict__) - - -# Example 16 -class GameState(object): - def __init__(self, level=0, points=0, magic=5): - self.level = level - self.points = points - self.magic = magic - - -# Example 17 -try: - pickle.loads(serialized) -except: - logging.exception('Expected') -else: - assert False - - -# Example 18 -def pickle_game_state(game_state): - kwargs = game_state.__dict__ - kwargs['version'] = 2 - return unpickle_game_state, (kwargs,) - - -# Example 19 -def unpickle_game_state(kwargs): - version = kwargs.pop('version', 1) - if version == 1: - kwargs.pop('lives') - return GameState(**kwargs) - - -# Example 20 -copyreg.pickle(GameState, pickle_game_state) -state_after = pickle.loads(serialized) -print(state_after.__dict__) - - -# Example 21 -copyreg.dispatch_table.clear() -state = GameState() -serialized = pickle.dumps(state) -del GameState -class BetterGameState(object): - def __init__(self, level=0, points=0, magic=5): - self.level = level - self.points = points - self.magic = magic - - -# Example 22 -try: - pickle.loads(serialized) -except: - logging.exception('Expected') -else: - assert False - - -# Example 23 -print(serialized[:25]) - - -# Example 24 -copyreg.pickle(BetterGameState, pickle_game_state) - - -# Example 25 -state = BetterGameState() -serialized = pickle.dumps(state) -print(serialized[:35]) diff --git a/example_code/item_45.py b/example_code/item_45.py deleted file mode 100755 index aa1cbf4..0000000 --- a/example_code/item_45.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -from time import localtime, strftime - -now = 1407694710 -local_tuple = localtime(now) -time_format = '%Y-%m-%d %H:%M:%S' -time_str = strftime(time_format, local_tuple) -print(time_str) - - -# Example 2 -from time import mktime, strptime - -time_tuple = strptime(time_str, time_format) -utc_now = mktime(time_tuple) -print(utc_now) - - -# Example 3 -parse_format = '%Y-%m-%d %H:%M:%S %Z' -depart_sfo = '2014-05-01 15:45:16 PDT' -time_tuple = strptime(depart_sfo, parse_format) -time_str = strftime(time_format, time_tuple) -print(time_str) - - -# Example 4 -try: - arrival_nyc = '2014-05-01 23:33:24 EDT' - time_tuple = strptime(arrival_nyc, time_format) -except: - logging.exception('Expected') -else: - assert False - - -# Example 5 -from datetime import datetime, timezone - -now = datetime(2014, 8, 10, 18, 18, 30) -now_utc = now.replace(tzinfo=timezone.utc) -now_local = now_utc.astimezone() -print(now_local) - - -# Example 6 -time_str = '2014-08-10 11:18:30' -now = datetime.strptime(time_str, time_format) -time_tuple = now.timetuple() -utc_now = mktime(time_tuple) -print(utc_now) - - -# Example 7 -import pytz -arrival_nyc = '2014-05-01 23:33:24' -nyc_dt_naive = datetime.strptime(arrival_nyc, time_format) -eastern = pytz.timezone('US/Eastern') -nyc_dt = eastern.localize(nyc_dt_naive) -utc_dt = pytz.utc.normalize(nyc_dt.astimezone(pytz.utc)) -print(utc_dt) - - -# Example 8 -pacific = pytz.timezone('US/Pacific') -sf_dt = pacific.normalize(utc_dt.astimezone(pacific)) -print(sf_dt) - - -# Example 9 -nepal = pytz.timezone('Asia/Katmandu') -nepal_dt = nepal.normalize(utc_dt.astimezone(nepal)) -print(nepal_dt) diff --git a/example_code/item_46.py b/example_code/item_46.py deleted file mode 100755 index a937260..0000000 --- a/example_code/item_46.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -from collections import deque -fifo = deque() -fifo.append(1) # Producer -fifo.append(2) -fifo.append(3) -x = fifo.popleft() # Consumer -print(x) - - -# Example 2 -a = {} -a['foo'] = 1 -a['bar'] = 2 -from random import randint - -# Randomly populate 'b' to cause hash conflicts -while True: - z = randint(99, 1013) - b = {} - for i in range(z): - b[i] = i - b['foo'] = 1 - b['bar'] = 2 - for i in range(z): - del b[i] - if str(b) != str(a): - break - -print(a) -print(b) -print('Equal?', a == b) - - -# Example 3 -from collections import OrderedDict -a = OrderedDict() -a['foo'] = 1 -a['bar'] = 2 - -b = OrderedDict() -b['foo'] = 'red' -b['bar'] = 'blue' - -for value1, value2 in zip(a.values(), b.values()): - print(value1, value2) - - -# Example 4 -stats = {} -key = 'my_counter' -if key not in stats: - stats[key] = 0 -stats[key] += 1 -print(stats) - - -# Example 5 -from collections import defaultdict -stats = defaultdict(int) -stats['my_counter'] += 1 -print(dict(stats)) - - -# Example 6 -from heapq import * -a = [] -heappush(a, 5) -heappush(a, 3) -heappush(a, 7) -heappush(a, 4) - - -# Example 7 -print(heappop(a), heappop(a), heappop(a), heappop(a)) - - -# Example 8 -a = [] -heappush(a, 5) -heappush(a, 3) -heappush(a, 7) -heappush(a, 4) -assert a[0] == nsmallest(1, a)[0] == 3 - - -# Example 9 -print('Before:', a) -a.sort() -print('After: ', a) - - -# Example 10 -x = list(range(10**6)) -i = x.index(991234) -print(i) - - -# Example 11 -from bisect import bisect_left -i = bisect_left(x, 991234) -print(i) - - -# Example 12 -from timeit import timeit -print(timeit( - 'a.index(len(a)-1)', - 'a = list(range(100))', - number=1000)) -print(timeit( - 'bisect_left(a, len(a)-1)', - 'from bisect import bisect_left;' - 'a = list(range(10**6))', - number=1000)) diff --git a/example_code/item_47.py b/example_code/item_47.py deleted file mode 100755 index 7d7ce92..0000000 --- a/example_code/item_47.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -rate = 1.45 -seconds = 3*60 + 42 -cost = rate * seconds / 60 -print(cost) - - -# Example 2 -print(round(cost, 2)) - - -# Example 3 -rate = 0.05 -seconds = 5 -cost = rate * seconds / 60 -print(cost) - - -# Example 4 -print(round(cost, 2)) - - -# Example 5 -from decimal import Decimal -from decimal import ROUND_UP -rate = Decimal('1.45') -seconds = Decimal('222') # 3*60 + 42 -cost = rate * seconds / Decimal('60') -print(cost) - - -# Example 6 -rounded = cost.quantize(Decimal('0.01'), rounding=ROUND_UP) -print(rounded) - - -# Example 7 -rate = Decimal('0.05') -seconds = Decimal('5') -cost = rate * seconds / Decimal('60') -print(cost) - - -# Example 8 -rounded = cost.quantize(Decimal('0.01'), rounding=ROUND_UP) -print(rounded) diff --git a/example_code/item_49.py b/example_code/item_49.py deleted file mode 100755 index a66bad5..0000000 --- a/example_code/item_49.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def palindrome(word): - """Return True if the given word is a palindrome.""" - return word == word[::-1] - -assert palindrome('tacocat') -assert not palindrome('banana') - - -# Example 2 -print(repr(palindrome.__doc__)) - - -# Example 3 -"""Library for testing words for various linguistic patterns. - -Testing how words relate to each other can be tricky sometimes! -This module provides easy ways to determine when words you've -found have special properties. - -Available functions: -- palindrome: Determine if a word is a palindrome. -- check_anagram: Determine if two words are anagrams. -... -""" - - -# Example 4 -class Player(object): - """Represents a player of the game. - - Subclasses may override the 'tick' method to provide - custom animations for the player's movement depending - on their power level, etc. - - Public attributes: - - power: Unused power-ups (float between 0 and 1). - - coins: Coins found during the level (integer). - """ - - -# Example 5 -import itertools -def find_anagrams(word, dictionary): - """Find all anagrams for a word. - - This function only runs as fast as the test for - membership in the 'dictionary' container. It will - be slow if the dictionary is a list and fast if - it's a set. - - Args: - word: String of the target word. - dictionary: Container with all strings that - are known to be actual words. - - Returns: - List of anagrams that were found. Empty if - none were found. - """ - permutations = itertools.permutations(word, len(word)) - possible = (''.join(x) for x in permutations) - found = {word for word in possible if word in dictionary} - return list(found) - -assert find_anagrams('pancakes', ['scanpeak']) == ['scanpeak'] diff --git a/example_code/item_50/api_package/api_consumer.py b/example_code/item_50/api_package/api_consumer.py deleted file mode 100755 index 6fd5283..0000000 --- a/example_code/item_50/api_package/api_consumer.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 12 -from mypackage import * - -a = Projectile(1.5, 3) -b = Projectile(4, 1.7) -after_a, after_b = simulate_collision(a, b) -print(after_a.__dict__, after_b.__dict__) - -import mypackage -try: - mypackage._dot_product - assert False -except AttributeError: - pass # Expected - -mypackage.utils._dot_product # But this is defined diff --git a/example_code/item_50/api_package/main.py b/example_code/item_50/api_package/main.py deleted file mode 100755 index b5087b3..0000000 --- a/example_code/item_50/api_package/main.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -from mypackage import utils diff --git a/example_code/item_50/api_package/mypackage/__init__.py b/example_code/item_50/api_package/mypackage/__init__.py deleted file mode 100755 index 3e55261..0000000 --- a/example_code/item_50/api_package/mypackage/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 11 -__all__ = [] -from . models import * -__all__ += models.__all__ -from . utils import * -__all__ += utils.__all__ diff --git a/example_code/item_50/api_package/mypackage/models.py b/example_code/item_50/api_package/mypackage/models.py deleted file mode 100755 index 1a58640..0000000 --- a/example_code/item_50/api_package/mypackage/models.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 9 -__all__ = ['Projectile'] - -class Projectile(object): - def __init__(self, mass, velocity): - self.mass = mass - self.velocity = velocity diff --git a/example_code/item_50/api_package/mypackage/utils.py b/example_code/item_50/api_package/mypackage/utils.py deleted file mode 100755 index 43a7cd7..0000000 --- a/example_code/item_50/api_package/mypackage/utils.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 10 -from . models import Projectile - -__all__ = ['simulate_collision'] - -def _dot_product(a, b): - pass - -def simulate_collision(a, b): - after_a = Projectile(-a.mass, -a.velocity) - after_b = Projectile(-b.mass, -b.velocity) - return after_a, after_b diff --git a/example_code/item_50/namespace_package/analysis/__init__.py b/example_code/item_50/namespace_package/analysis/__init__.py deleted file mode 100755 index 7302c16..0000000 --- a/example_code/item_50/namespace_package/analysis/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 2 - diff --git a/example_code/item_50/namespace_package/analysis/utils.py b/example_code/item_50/namespace_package/analysis/utils.py deleted file mode 100755 index 058083f..0000000 --- a/example_code/item_50/namespace_package/analysis/utils.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 3 -import math -def log_base2_bucket(value): - return math.log(value, 2) - -def inspect(value): - pass diff --git a/example_code/item_50/namespace_package/frontend/__init__.py b/example_code/item_50/namespace_package/frontend/__init__.py deleted file mode 100755 index a0d67b6..0000000 --- a/example_code/item_50/namespace_package/frontend/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 4 - diff --git a/example_code/item_50/namespace_package/frontend/utils.py b/example_code/item_50/namespace_package/frontend/utils.py deleted file mode 100755 index 2cb6a65..0000000 --- a/example_code/item_50/namespace_package/frontend/utils.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 5 -def stringify(value): - return str(value) - -def inspect(value): - pass diff --git a/example_code/item_50/namespace_package/main.py b/example_code/item_50/namespace_package/main.py deleted file mode 100755 index 37ce7ff..0000000 --- a/example_code/item_50/namespace_package/main.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 6 -from analysis.utils import log_base2_bucket -from frontend.utils import stringify - -bucket = stringify(log_base2_bucket(33)) -print(repr(bucket)) diff --git a/example_code/item_50/namespace_package/main2.py b/example_code/item_50/namespace_package/main2.py deleted file mode 100755 index 9f10541..0000000 --- a/example_code/item_50/namespace_package/main2.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 7 -from analysis.utils import inspect -from frontend.utils import inspect # Overwrites! -'frontend' in inspect.__module__ -print(inspect.__module__) diff --git a/example_code/item_50/namespace_package/main3.py b/example_code/item_50/namespace_package/main3.py deleted file mode 100755 index adb3810..0000000 --- a/example_code/item_50/namespace_package/main3.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 8 -from analysis.utils import inspect as analysis_inspect -from frontend.utils import inspect as frontend_inspect - -value = 33 -if analysis_inspect(value) == frontend_inspect(value): - print('Inspection equal!') diff --git a/example_code/item_51.py b/example_code/item_51.py deleted file mode 100755 index a911fbe..0000000 --- a/example_code/item_51.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -try: - def determine_weight(volume, density): - if density <= 0: - raise ValueError('Density must be positive') - - determine_weight(1, 0) -except: - logging.exception('Expected') -else: - assert False - - -# Example 2 -# my_module.py -class Error(Exception): - """Base-class for all exceptions raised by this module.""" - -class InvalidDensityError(Error): - """There was a problem with a provided density value.""" - - -# Example 3 -class my_module(object): - Error = Error - InvalidDensityError = InvalidDensityError - - @staticmethod - def determine_weight(volume, density): - if density <= 0: - raise InvalidDensityError('Density must be positive') -try: - weight = my_module.determine_weight(1, -1) - assert False -except my_module.Error as e: - logging.error('Unexpected error: %s', e) - - -# Example 4 -weight = 5 -try: - weight = my_module.determine_weight(1, -1) - assert False -except my_module.InvalidDensityError: - weight = 0 -except my_module.Error as e: - logging.error('Bug in the calling code: %s', e) - -assert weight == 0 - - -# Example 5 -weight = 5 -try: - weight = my_module.determine_weight(1, -1) - assert False -except my_module.InvalidDensityError: - weight = 0 -except my_module.Error as e: - logging.error('Bug in the calling code: %s', e) -except Exception as e: - logging.error('Bug in the API code: %s', e) - raise - -assert weight == 0 - - -# Example 6 -# my_module.py -class NegativeDensityError(InvalidDensityError): - """A provided density value was negative.""" - -def determine_weight(volume, density): - if density < 0: - raise NegativeDensityError - - -# Example 7 -try: - my_module.NegativeDensityError = NegativeDensityError - my_module.determine_weight = determine_weight - try: - weight = my_module.determine_weight(1, -1) - assert False - except my_module.NegativeDensityError as e: - raise ValueError('Must supply non-negative density') from e - except my_module.InvalidDensityError: - weight = 0 - except my_module.Error as e: - logging.error('Bug in the calling code: %s', e) - except Exception as e: - logging.error('Bug in the API code: %s', e) - raise -except: - logging.exception('Expected') -else: - assert False - - -# Example 8 -# my_module.py -class WeightError(Error): - """Base-class for weight calculation errors.""" - -class VolumeError(Error): - """Base-class for volume calculation errors.""" - -class DensityError(Error): - """Base-class for density calculation errors.""" diff --git a/example_code/item_52/recursive_import_bad/app.py b/example_code/item_52/recursive_import_bad/app.py deleted file mode 100755 index 82139af..0000000 --- a/example_code/item_52/recursive_import_bad/app.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 3 -import dialog - -class Prefs(object): - def get(self, name): - pass - -prefs = Prefs() -dialog.show() diff --git a/example_code/item_52/recursive_import_bad/dialog.py b/example_code/item_52/recursive_import_bad/dialog.py deleted file mode 100755 index adf7de7..0000000 --- a/example_code/item_52/recursive_import_bad/dialog.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 2 -import app - -class Dialog(object): - def __init__(self, save_dir): - self.save_dir = save_dir - -save_dialog = Dialog(app.prefs.get('save_dir')) - -def show(): - print('Showing the dialog!') diff --git a/example_code/item_52/recursive_import_bad/main.py b/example_code/item_52/recursive_import_bad/main.py deleted file mode 100755 index 14d24c6..0000000 --- a/example_code/item_52/recursive_import_bad/main.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -import app diff --git a/example_code/item_52/recursive_import_dynamic/app.py b/example_code/item_52/recursive_import_dynamic/app.py deleted file mode 100755 index 75fe7a6..0000000 --- a/example_code/item_52/recursive_import_dynamic/app.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 12 -import dialog - -class Prefs(object): - def get(self, name): - pass - -prefs = Prefs() -dialog.show() diff --git a/example_code/item_52/recursive_import_dynamic/dialog.py b/example_code/item_52/recursive_import_dynamic/dialog.py deleted file mode 100755 index d754019..0000000 --- a/example_code/item_52/recursive_import_dynamic/dialog.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 11 -# Reenabling this will break things. -# import app - -class Dialog(object): - def __init__(self): - pass - -# Using this instead will break things -# save_dialog = Dialog(app.prefs.get('save_dir')) -save_dialog = Dialog() - -def show(): - import app # Dynamic import - save_dialog.save_dir = app.prefs.get('save_dir') - print('Showing the dialog!') diff --git a/example_code/item_52/recursive_import_dynamic/main.py b/example_code/item_52/recursive_import_dynamic/main.py deleted file mode 100755 index 7ffd123..0000000 --- a/example_code/item_52/recursive_import_dynamic/main.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 10 -import app diff --git a/example_code/item_52/recursive_import_nosideeffects/app.py b/example_code/item_52/recursive_import_nosideeffects/app.py deleted file mode 100755 index 4cbce45..0000000 --- a/example_code/item_52/recursive_import_nosideeffects/app.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 8 -import dialog - -class Prefs(object): - def get(self, name): - pass - -prefs = Prefs() - -def configure(): - pass diff --git a/example_code/item_52/recursive_import_nosideeffects/dialog.py b/example_code/item_52/recursive_import_nosideeffects/dialog.py deleted file mode 100755 index 61fe151..0000000 --- a/example_code/item_52/recursive_import_nosideeffects/dialog.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 7 -import app - -class Dialog(object): - def __init__(self): - pass - -save_dialog = Dialog() - -def show(): - print('Showing the dialog!') - -def configure(): - save_dialog.save_dir = app.prefs.get('save_dir') diff --git a/example_code/item_52/recursive_import_nosideeffects/main.py b/example_code/item_52/recursive_import_nosideeffects/main.py deleted file mode 100755 index 27174fd..0000000 --- a/example_code/item_52/recursive_import_nosideeffects/main.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 9 -import app -import dialog - -app.configure() -dialog.configure() - -dialog.show() diff --git a/example_code/item_52/recursive_import_ordering/app.py b/example_code/item_52/recursive_import_ordering/app.py deleted file mode 100755 index 296116e..0000000 --- a/example_code/item_52/recursive_import_ordering/app.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 6 -class Prefs(object): - def get(self, name): - pass - -prefs = Prefs() - -import dialog # Moved -dialog.show() diff --git a/example_code/item_52/recursive_import_ordering/dialog.py b/example_code/item_52/recursive_import_ordering/dialog.py deleted file mode 100755 index 95c86a8..0000000 --- a/example_code/item_52/recursive_import_ordering/dialog.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 4 -import app - -class Dialog(object): - def __init__(self, save_dir): - self.save_dir = save_dir - -save_dialog = Dialog(app.prefs.get('save_dir')) - -def show(): - print('Showing the dialog!') diff --git a/example_code/item_52/recursive_import_ordering/main.py b/example_code/item_52/recursive_import_ordering/main.py deleted file mode 100755 index dbd0992..0000000 --- a/example_code/item_52/recursive_import_ordering/main.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 5 -import app diff --git a/example_code/item_54.py b/example_code/item_54.py deleted file mode 100755 index 2973d5a..0000000 --- a/example_code/item_54.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 4 -# db_connection.py -import sys - -class Win32Database(object): - pass - -class PosixDatabase(object): - pass - -if sys.platform.startswith('win32'): - Database = Win32Database -else: - Database = PosixDatabase diff --git a/example_code/item_54/module_scope/db_connection.py b/example_code/item_54/module_scope/db_connection.py deleted file mode 100755 index 5c4a0c1..0000000 --- a/example_code/item_54/module_scope/db_connection.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 3 -# db_connection.py -import __main__ - -class TestingDatabase(object): - pass - -class RealDatabase(object): - pass - -if __main__.TESTING: - Database = TestingDatabase -else: - Database = RealDatabase diff --git a/example_code/item_54/module_scope/dev_main.py b/example_code/item_54/module_scope/dev_main.py deleted file mode 100755 index 3e5a682..0000000 --- a/example_code/item_54/module_scope/dev_main.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -TESTING = True -import db_connection -db = db_connection.Database() diff --git a/example_code/item_54/module_scope/prod_main.py b/example_code/item_54/module_scope/prod_main.py deleted file mode 100755 index 88924be..0000000 --- a/example_code/item_54/module_scope/prod_main.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 2 -TESTING = False -import db_connection -db = db_connection.Database() diff --git a/example_code/item_55.py b/example_code/item_55.py deleted file mode 100755 index bb5f9e1..0000000 --- a/example_code/item_55.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -print('foo bar') - - -# Example 2 -print('%s' % 'foo bar') - - -# Example 3 -print(5) -print('5') - - -# Example 4 -a = '\x07' -print(repr(a)) - - -# Example 5 -b = eval(repr(a)) -assert a == b - - -# Example 6 -print(repr(5)) -print(repr('5')) - - -# Example 7 -print('%r' % 5) -print('%r' % '5') - - -# Example 8 -class OpaqueClass(object): - def __init__(self, x, y): - self.x = x - self.y = y - -obj = OpaqueClass(1, 2) -print(obj) - - -# Example 9 -class BetterClass(object): - def __init__(self, x, y): - self.x = 1 - self.y = 2 - def __repr__(self): - return 'BetterClass(%d, %d)' % (self.x, self.y) - - -# Example 10 -obj = BetterClass(1, 2) -print(obj) - - -# Example 11 -obj = OpaqueClass(4, 5) -print(obj.__dict__) diff --git a/example_code/item_56.py b/example_code/item_56.py deleted file mode 100755 index 05ffbc7..0000000 --- a/example_code/item_56.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 3 -from tempfile import TemporaryDirectory -from unittest import TestCase -class MyTest(TestCase): - def setUp(self): - self.test_dir = TemporaryDirectory() - def tearDown(self): - self.test_dir.cleanup() - # Test methods follow diff --git a/example_code/item_56/testing/utils.py b/example_code/item_56/testing/utils.py deleted file mode 100755 index 8403358..0000000 --- a/example_code/item_56/testing/utils.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def to_str(data): - if isinstance(data, str): - return data - elif isinstance(data, bytes): - return data.decode('utf-8') - else: - raise TypeError('Must supply str or bytes, ' - 'found: %r' % data) diff --git a/example_code/item_56/testing/utils_test.py b/example_code/item_56/testing/utils_test.py deleted file mode 100755 index e38896c..0000000 --- a/example_code/item_56/testing/utils_test.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 2 -from unittest import TestCase, main -from utils import to_str - -class UtilsTestCase(TestCase): - def test_to_str_bytes(self): - self.assertEqual('hello', to_str(b'hello')) - - def test_to_str_str(self): - self.assertEqual('hello', to_str('hello')) - - def test_to_str_bad(self): - self.assertRaises(TypeError, to_str, object()) - -if __name__ == '__main__': - main() diff --git a/example_code/item_58.py b/example_code/item_58.py deleted file mode 100755 index 1c64e6e..0000000 --- a/example_code/item_58.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def insertion_sort(data): - result = [] - for value in data: - insert_value(result, value) - return result - - -# Example 2 -def insert_value(array, value): - for i, existing in enumerate(array): - if existing > value: - array.insert(i, value) - return - array.append(value) - - -# Example 3 -from random import randint - -max_size = 10**4 -data = [randint(0, max_size) for _ in range(max_size)] -test = lambda: insertion_sort(data) - - -# Example 4 -from cProfile import Profile - -profiler = Profile() -profiler.runcall(test) - - -# Example 5 -import sys -from pstats import Stats - -stats = Stats(profiler) -stats = Stats(profiler, stream=STDOUT) -stats.strip_dirs() -stats.sort_stats('cumulative') -stats.print_stats() - - -# Example 6 -from bisect import bisect_left - -def insert_value(array, value): - i = bisect_left(array, value) - array.insert(i, value) - -profiler = Profile() -profiler.runcall(test) -stats = Stats(profiler, stream=STDOUT) -stats.strip_dirs() -stats.sort_stats('cumulative') -stats.print_stats() - - -# Example 7 -def my_utility(a, b): - c = 1 - for i in range(100): - c += a * b - -def first_func(): - for _ in range(1000): - my_utility(4, 5) - -def second_func(): - for _ in range(10): - my_utility(1, 3) - -def my_program(): - for _ in range(20): - first_func() - second_func() - - -# Example 8 -profiler = Profile() -profiler.runcall(my_program) -stats = Stats(profiler, stream=STDOUT) -stats.strip_dirs() -stats.sort_stats('cumulative') -stats.print_stats() - - -# Example 9 -stats = Stats(profiler, stream=STDOUT) -stats.strip_dirs() -stats.sort_stats('cumulative') -stats.print_callers() diff --git a/example_code/item_59/tracemalloc/top_n.py b/example_code/item_59/tracemalloc/top_n.py deleted file mode 100755 index 82a7b76..0000000 --- a/example_code/item_59/tracemalloc/top_n.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 3 -import tracemalloc -tracemalloc.start(10) # Save up to 10 stack frames - -time1 = tracemalloc.take_snapshot() -import waste_memory -x = waste_memory.run() -time2 = tracemalloc.take_snapshot() - -stats = time2.compare_to(time1, 'lineno') -for stat in stats[:3]: - print(stat) diff --git a/example_code/item_59/tracemalloc/using_gc.py b/example_code/item_59/tracemalloc/using_gc.py deleted file mode 100755 index a116265..0000000 --- a/example_code/item_59/tracemalloc/using_gc.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 2 -import gc -found_objects = gc.get_objects() -print('%d objects before' % len(found_objects)) - -import waste_memory -x = waste_memory.run() -found_objects = gc.get_objects() -print('%d objects after' % len(found_objects)) -for obj in found_objects[:3]: - print(repr(obj)[:100]) diff --git a/example_code/item_59/tracemalloc/waste_memory.py b/example_code/item_59/tracemalloc/waste_memory.py deleted file mode 100755 index 4ea62e3..0000000 --- a/example_code/item_59/tracemalloc/waste_memory.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -import os -import hashlib - -class MyObject(object): - def __init__(self): - self.x = os.urandom(100) - self.y = hashlib.sha1(self.x).hexdigest() - -def get_data(): - values = [] - for _ in range(100): - obj = MyObject() - values.append(obj) - return values - -def run(): - deep_values = [] - for _ in range(100): - deep_values.append(get_data()) - return deep_values diff --git a/example_code/item_59/tracemalloc/with_trace.py b/example_code/item_59/tracemalloc/with_trace.py deleted file mode 100755 index 1942ee6..0000000 --- a/example_code/item_59/tracemalloc/with_trace.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 4 -import tracemalloc -tracemalloc.start(10) - -time1 = tracemalloc.take_snapshot() -import waste_memory -x = waste_memory.run() -time2 = tracemalloc.take_snapshot() -stats = time2.compare_to(time1, 'traceback') -top = stats[0] -print('\n'.join(top.traceback.format())) From 7e75b477d26a52fc49e06f98046e90ee1993720a Mon Sep 17 00:00:00 2001 From: Brett Slatkin Date: Sat, 5 Oct 2019 17:13:06 -0700 Subject: [PATCH 04/12] Adding new example code --- example_code/item_01.py | 52 ++ example_code/item_03.py | 199 ++++++++ example_code/item_04.py | 311 ++++++++++++ example_code/item_05.py | 113 +++++ example_code/item_06.py | 135 ++++++ example_code/item_07.py | 85 ++++ example_code/item_08.py | 99 ++++ example_code/item_09.py | 113 +++++ example_code/item_10.py | 242 ++++++++++ example_code/item_11.py | 131 +++++ example_code/item_12.py | 99 ++++ example_code/item_13.py | 147 ++++++ example_code/item_14.py | 166 +++++++ example_code/item_15.py | 186 +++++++ example_code/item_15_example_01.py | 25 + example_code/item_15_example_03.py | 28 ++ example_code/item_15_example_05.py | 25 + example_code/item_15_example_07.py | 28 ++ example_code/item_15_example_17.py | 68 +++ example_code/item_16.py | 198 ++++++++ example_code/item_17.py | 99 ++++ example_code/item_18.py | 191 ++++++++ example_code/item_19.py | 142 ++++++ example_code/item_20.py | 143 ++++++ example_code/item_21.py | 141 ++++++ example_code/item_22.py | 100 ++++ example_code/item_23.py | 161 +++++++ example_code/item_24.py | 128 +++++ example_code/item_24_example_09.py | 41 ++ example_code/item_25.py | 238 +++++++++ example_code/item_26.py | 126 +++++ example_code/item_27.py | 90 ++++ example_code/item_28.py | 93 ++++ example_code/item_29.py | 137 ++++++ example_code/item_30.py | 113 +++++ example_code/item_31.py | 209 ++++++++ example_code/item_32.py | 76 +++ example_code/item_33.py | 117 +++++ example_code/item_34.py | 171 +++++++ example_code/item_35.py | 157 ++++++ example_code/item_36.py | 162 +++++++ example_code/item_37.py | 230 +++++++++ example_code/item_38.py | 138 ++++++ example_code/item_39.py | 209 ++++++++ example_code/item_40.py | 174 +++++++ example_code/item_41.py | 177 +++++++ example_code/item_42.py | 214 +++++++++ example_code/item_43.py | 208 ++++++++ example_code/item_44.py | 192 ++++++++ example_code/item_45.py | 162 +++++++ example_code/item_46.py | 227 +++++++++ example_code/item_47.py | 195 ++++++++ example_code/item_48.py | 303 ++++++++++++ example_code/item_49.py | 212 ++++++++ example_code/item_50.py | 182 +++++++ example_code/item_51.py | 243 ++++++++++ example_code/item_52.py | 182 +++++++ example_code/item_53.py | 142 ++++++ example_code/item_54.py | 144 ++++++ example_code/item_55.py | 325 +++++++++++++ example_code/item_56.py | 228 +++++++++ example_code/item_57.py | 211 ++++++++ example_code/item_58.py | 413 ++++++++++++++++ example_code/item_59.py | 207 ++++++++ example_code/item_60.py | 247 ++++++++++ example_code/item_61.py | 454 ++++++++++++++++++ example_code/item_62.py | 295 ++++++++++++ example_code/item_63.py | 252 ++++++++++ example_code/item_64/parallel/my_module.py | 23 + example_code/item_64/parallel/run_parallel.py | 38 ++ example_code/item_64/parallel/run_serial.py | 36 ++ example_code/item_64/parallel/run_threads.py | 38 ++ example_code/item_65.py | 197 ++++++++ example_code/item_66.py | 136 ++++++ example_code/item_67.py | 125 +++++ example_code/item_68.py | 224 +++++++++ example_code/item_69.py | 99 ++++ example_code/item_70.py | 141 ++++++ example_code/item_71.py | 264 ++++++++++ example_code/item_72.py | 115 +++++ example_code/item_73.py | 384 +++++++++++++++ example_code/item_74.py | 208 ++++++++ example_code/item_75.py | 123 +++++ example_code/item_76/testing/assert_test.py | 32 ++ .../item_76/testing/data_driven_test.py | 42 ++ example_code/item_76/testing/helper_test.py | 69 +++ example_code/item_76/testing/utils.py | 24 + .../item_76/testing/utils_error_test.py | 30 ++ example_code/item_76/testing/utils_test.py | 31 ++ .../item_77/testing/environment_test.py | 34 ++ .../item_77/testing/integration_test.py | 39 ++ example_code/item_78.py | 307 ++++++++++++ example_code/item_79.py | 166 +++++++ .../item_80/debugging/always_breakpoint.py | 35 ++ .../debugging/conditional_breakpoint.py | 34 ++ example_code/item_80/debugging/my_module.py | 31 ++ .../debugging/postmortem_breakpoint.py | 34 ++ example_code/item_81/tracemalloc/top_n.py | 29 ++ example_code/item_81/tracemalloc/using_gc.py | 31 ++ .../item_81/tracemalloc/waste_memory.py | 35 ++ .../item_81/tracemalloc/with_trace.py | 30 ++ example_code/item_84.py | 112 +++++ example_code/item_84_example_06.py | 26 + example_code/item_84_example_07.py | 38 ++ .../item_85/api_package/api_consumer.py | 31 ++ example_code/item_85/api_package/main.py | 17 + .../item_85/api_package/mypackage/__init__.py | 21 + .../item_85/api_package/mypackage/models.py | 22 + .../item_85/api_package/mypackage/utils.py | 27 ++ .../namespace_package/analysis/__init__.py | 17 + .../namespace_package/analysis/utils.py | 22 + .../namespace_package/frontend/__init__.py | 17 + .../namespace_package/frontend/utils.py | 21 + .../item_85/namespace_package/main.py | 21 + .../item_85/namespace_package/main2.py | 20 + .../item_85/namespace_package/main3.py | 22 + .../item_85/namespace_package/main4.py | 23 + example_code/item_86.py | 62 +++ .../item_86/module_scope/db_connection.py | 29 ++ example_code/item_86/module_scope/dev_main.py | 21 + .../item_86/module_scope/prod_main.py | 21 + example_code/item_87.py | 189 ++++++++ .../item_88/recursive_import_bad/app.py | 24 + .../item_88/recursive_import_bad/dialog.py | 26 + .../item_88/recursive_import_bad/main.py | 17 + .../item_88/recursive_import_dynamic/app.py | 24 + .../recursive_import_dynamic/dialog.py | 31 ++ .../item_88/recursive_import_dynamic/main.py | 17 + .../recursive_import_nosideeffects/app.py | 26 + .../recursive_import_nosideeffects/dialog.py | 29 ++ .../recursive_import_nosideeffects/main.py | 23 + .../item_88/recursive_import_ordering/app.py | 24 + .../recursive_import_ordering/dialog.py | 26 + .../item_88/recursive_import_ordering/main.py | 17 + example_code/item_89.py | 234 +++++++++ example_code/item_90.py | 191 ++++++++ example_code/item_90_example_02.py | 25 + example_code/item_90_example_04.py | 25 + example_code/item_90_example_08.py | 35 ++ example_code/item_90_example_10.py | 43 ++ example_code/item_90_example_12.py | 28 ++ example_code/item_90_example_14.py | 31 ++ example_code/item_90_example_17.py | 31 ++ 143 files changed, 16341 insertions(+) create mode 100755 example_code/item_01.py create mode 100755 example_code/item_03.py create mode 100755 example_code/item_04.py create mode 100755 example_code/item_05.py create mode 100755 example_code/item_06.py create mode 100755 example_code/item_07.py create mode 100755 example_code/item_08.py create mode 100755 example_code/item_09.py create mode 100755 example_code/item_10.py create mode 100755 example_code/item_11.py create mode 100755 example_code/item_12.py create mode 100755 example_code/item_13.py create mode 100755 example_code/item_14.py create mode 100755 example_code/item_15.py create mode 100755 example_code/item_15_example_01.py create mode 100755 example_code/item_15_example_03.py create mode 100755 example_code/item_15_example_05.py create mode 100755 example_code/item_15_example_07.py create mode 100755 example_code/item_15_example_17.py create mode 100755 example_code/item_16.py create mode 100755 example_code/item_17.py create mode 100755 example_code/item_18.py create mode 100755 example_code/item_19.py create mode 100755 example_code/item_20.py create mode 100755 example_code/item_21.py create mode 100755 example_code/item_22.py create mode 100755 example_code/item_23.py create mode 100755 example_code/item_24.py create mode 100755 example_code/item_24_example_09.py create mode 100755 example_code/item_25.py create mode 100755 example_code/item_26.py create mode 100755 example_code/item_27.py create mode 100755 example_code/item_28.py create mode 100755 example_code/item_29.py create mode 100755 example_code/item_30.py create mode 100755 example_code/item_31.py create mode 100755 example_code/item_32.py create mode 100755 example_code/item_33.py create mode 100755 example_code/item_34.py create mode 100755 example_code/item_35.py create mode 100755 example_code/item_36.py create mode 100755 example_code/item_37.py create mode 100755 example_code/item_38.py create mode 100755 example_code/item_39.py create mode 100755 example_code/item_40.py create mode 100755 example_code/item_41.py create mode 100755 example_code/item_42.py create mode 100755 example_code/item_43.py create mode 100755 example_code/item_44.py create mode 100755 example_code/item_45.py create mode 100755 example_code/item_46.py create mode 100755 example_code/item_47.py create mode 100755 example_code/item_48.py create mode 100755 example_code/item_49.py create mode 100755 example_code/item_50.py create mode 100755 example_code/item_51.py create mode 100755 example_code/item_52.py create mode 100755 example_code/item_53.py create mode 100755 example_code/item_54.py create mode 100755 example_code/item_55.py create mode 100755 example_code/item_56.py create mode 100755 example_code/item_57.py create mode 100755 example_code/item_58.py create mode 100755 example_code/item_59.py create mode 100755 example_code/item_60.py create mode 100755 example_code/item_61.py create mode 100755 example_code/item_62.py create mode 100755 example_code/item_63.py create mode 100755 example_code/item_64/parallel/my_module.py create mode 100755 example_code/item_64/parallel/run_parallel.py create mode 100755 example_code/item_64/parallel/run_serial.py create mode 100755 example_code/item_64/parallel/run_threads.py create mode 100755 example_code/item_65.py create mode 100755 example_code/item_66.py create mode 100755 example_code/item_67.py create mode 100755 example_code/item_68.py create mode 100755 example_code/item_69.py create mode 100755 example_code/item_70.py create mode 100755 example_code/item_71.py create mode 100755 example_code/item_72.py create mode 100755 example_code/item_73.py create mode 100755 example_code/item_74.py create mode 100755 example_code/item_75.py create mode 100755 example_code/item_76/testing/assert_test.py create mode 100755 example_code/item_76/testing/data_driven_test.py create mode 100755 example_code/item_76/testing/helper_test.py create mode 100755 example_code/item_76/testing/utils.py create mode 100755 example_code/item_76/testing/utils_error_test.py create mode 100755 example_code/item_76/testing/utils_test.py create mode 100755 example_code/item_77/testing/environment_test.py create mode 100755 example_code/item_77/testing/integration_test.py create mode 100755 example_code/item_78.py create mode 100755 example_code/item_79.py create mode 100755 example_code/item_80/debugging/always_breakpoint.py create mode 100755 example_code/item_80/debugging/conditional_breakpoint.py create mode 100755 example_code/item_80/debugging/my_module.py create mode 100755 example_code/item_80/debugging/postmortem_breakpoint.py create mode 100755 example_code/item_81/tracemalloc/top_n.py create mode 100755 example_code/item_81/tracemalloc/using_gc.py create mode 100755 example_code/item_81/tracemalloc/waste_memory.py create mode 100755 example_code/item_81/tracemalloc/with_trace.py create mode 100755 example_code/item_84.py create mode 100755 example_code/item_84_example_06.py create mode 100755 example_code/item_84_example_07.py create mode 100755 example_code/item_85/api_package/api_consumer.py create mode 100755 example_code/item_85/api_package/main.py create mode 100755 example_code/item_85/api_package/mypackage/__init__.py create mode 100755 example_code/item_85/api_package/mypackage/models.py create mode 100755 example_code/item_85/api_package/mypackage/utils.py create mode 100755 example_code/item_85/namespace_package/analysis/__init__.py create mode 100755 example_code/item_85/namespace_package/analysis/utils.py create mode 100755 example_code/item_85/namespace_package/frontend/__init__.py create mode 100755 example_code/item_85/namespace_package/frontend/utils.py create mode 100755 example_code/item_85/namespace_package/main.py create mode 100755 example_code/item_85/namespace_package/main2.py create mode 100755 example_code/item_85/namespace_package/main3.py create mode 100755 example_code/item_85/namespace_package/main4.py create mode 100755 example_code/item_86.py create mode 100755 example_code/item_86/module_scope/db_connection.py create mode 100755 example_code/item_86/module_scope/dev_main.py create mode 100755 example_code/item_86/module_scope/prod_main.py create mode 100755 example_code/item_87.py create mode 100755 example_code/item_88/recursive_import_bad/app.py create mode 100755 example_code/item_88/recursive_import_bad/dialog.py create mode 100755 example_code/item_88/recursive_import_bad/main.py create mode 100755 example_code/item_88/recursive_import_dynamic/app.py create mode 100755 example_code/item_88/recursive_import_dynamic/dialog.py create mode 100755 example_code/item_88/recursive_import_dynamic/main.py create mode 100755 example_code/item_88/recursive_import_nosideeffects/app.py create mode 100755 example_code/item_88/recursive_import_nosideeffects/dialog.py create mode 100755 example_code/item_88/recursive_import_nosideeffects/main.py create mode 100755 example_code/item_88/recursive_import_ordering/app.py create mode 100755 example_code/item_88/recursive_import_ordering/dialog.py create mode 100755 example_code/item_88/recursive_import_ordering/main.py create mode 100755 example_code/item_89.py create mode 100755 example_code/item_90.py create mode 100755 example_code/item_90_example_02.py create mode 100755 example_code/item_90_example_04.py create mode 100755 example_code/item_90_example_08.py create mode 100755 example_code/item_90_example_10.py create mode 100755 example_code/item_90_example_12.py create mode 100755 example_code/item_90_example_14.py create mode 100755 example_code/item_90_example_17.py diff --git a/example_code/item_01.py b/example_code/item_01.py new file mode 100755 index 0000000..585a9c5 --- /dev/null +++ b/example_code/item_01.py @@ -0,0 +1,52 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +import sys +print(sys.version_info) +print(sys.version) diff --git a/example_code/item_03.py b/example_code/item_03.py new file mode 100755 index 0000000..24eb0a7 --- /dev/null +++ b/example_code/item_03.py @@ -0,0 +1,199 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +a = b'h\x65llo' +print(list(a)) +print(a) + + +# Example 2 +a = 'a\u0300 propos' +print(list(a)) +print(a) + + +# Example 3 +def to_str(bytes_or_str): + if isinstance(bytes_or_str, bytes): + value = bytes_or_str.decode('utf-8') + else: + value = bytes_or_str + return value # Instance of str + +print(repr(to_str(b'foo'))) +print(repr(to_str('bar'))) + + +# Example 4 +def to_bytes(bytes_or_str): + if isinstance(bytes_or_str, str): + value = bytes_or_str.encode('utf-8') + else: + value = bytes_or_str + return value # Instance of bytes + +print(repr(to_bytes(b'foo'))) +print(repr(to_bytes('bar'))) + + +# Example 5 +print(b'one' + b'two') +print('one' + 'two') + + +# Example 6 +try: + b'one' + 'two' +except: + logging.exception('Expected') +else: + assert False + + +# Example 7 +try: + 'one' + b'two' +except: + logging.exception('Expected') +else: + assert False + + +# Example 8 +assert b'red' > b'blue' +assert 'red' > 'blue' + + +# Example 9 +try: + assert 'red' > b'blue' +except: + logging.exception('Expected') +else: + assert False + + +# Example 10 +try: + assert b'blue' < 'red' +except: + logging.exception('Expected') +else: + assert False + + +# Example 11 +print(b'foo' == 'foo') + + +# Example 12 +print(b'red %s' % b'blue') +print('red %s' % 'blue') + + +# Example 13 +try: + print(b'red %s' % 'blue') +except: + logging.exception('Expected') +else: + assert False + + +# Example 14 +print('red %s' % b'blue') + + +# Example 15 +try: + with open('data.bin', 'w') as f: + f.write(b'\xf1\xf2\xf3\xf4\xf5') +except: + logging.exception('Expected') +else: + assert False + + +# Example 16 +with open('data.bin', 'wb') as f: + f.write(b'\xf1\xf2\xf3\xf4\xf5') + + +# Example 17 +try: + # Silently force UTF-8 here to make sure this test fails on + # all platforms. cp1252 considers these bytes valid on Windows. + real_open = open + def open(*args, **kwargs): + kwargs['encoding'] = 'utf-8' + return real_open(*args, **kwargs) + + with open('data.bin', 'r') as f: + data = f.read() +except: + logging.exception('Expected') +else: + assert False + + +# Example 18 +# Restore the overloaded open above. +open = real_open + +with open('data.bin', 'rb') as f: + data = f.read() + +assert data == b'\xf1\xf2\xf3\xf4\xf5' + + +# Example 19 +with open('data.bin', 'r', encoding='cp1252') as f: + data = f.read() + +assert data == 'ñòóôõ' diff --git a/example_code/item_04.py b/example_code/item_04.py new file mode 100755 index 0000000..9efce73 --- /dev/null +++ b/example_code/item_04.py @@ -0,0 +1,311 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +a = 0b10111011 +b = 0xc5f +print('Binary is %d, hex is %d' % (a, b)) + + +# Example 2 +key = 'my_var' +value = 1.234 +formatted = '%-10s = %.2f' % (key, value) +print(formatted) + + +# Example 3 +try: + reordered_tuple = '%-10s = %.2f' % (value, key) +except: + logging.exception('Expected') +else: + assert False + + +# Example 4 +try: + reordered_string = '%.2f = %-10s' % (key, value) +except: + logging.exception('Expected') +else: + assert False + + +# Example 5 +pantry = [ + ('avocados', 1.25), + ('bananas', 2.5), + ('cherries', 15), +] +for i, (item, count) in enumerate(pantry): + print('#%d: %-10s = %.2f' % (i, item, count)) + + +# Example 6 +for i, (item, count) in enumerate(pantry): + print('#%d: %-10s = %d' % ( + i + 1, + item.title(), + round(count))) + + +# Example 7 +template = '%s loves food. See %s cook.' +name = 'Max' +formatted = template % (name, name) +print(formatted) + + +# Example 8 +name = 'brad' +formatted = template % (name.title(), name.title()) +print(formatted) + + +# Example 9 +key = 'my_var' +value = 1.234 + +old_way = '%-10s = %.2f' % (key, value) + +new_way = '%(key)-10s = %(value).2f' % { + 'key': key, 'value': value} # Original + +reordered = '%(key)-10s = %(value).2f' % { + 'value': value, 'key': key} # Swapped + +assert old_way == new_way == reordered + + +# Example 10 +name = 'Max' + +template = '%s loves food. See %s cook.' +before = template % (name, name) # Tuple + +template = '%(name)s loves food. See %(name)s cook.' +after = template % {'name': name} # Dictionary + +assert before == after + + +# Example 11 +for i, (item, count) in enumerate(pantry): + before = '#%d: %-10s = %d' % ( + i + 1, + item.title(), + round(count)) + + after = '#%(loop)d: %(item)-10s = %(count)d' % { + 'loop': i + 1, + 'item': item.title(), + 'count': round(count), + } + + assert before == after + + +# Example 12 +soup = 'lentil' +formatted = 'Today\'s soup is %(soup)s.' % {'soup': soup} +print(formatted) + + +# Example 13 +menu = { + 'soup': 'lentil', + 'oyster': 'kumamoto', + 'special': 'schnitzel', +} +template = ('Today\'s soup is %(soup)s, ' + 'buy one get two %(oyster)s oysters, ' + 'and our special entrée is %(special)s.') +formatted = template % menu +print(formatted) + + +# Example 14 +a = 1234.5678 +formatted = format(a, ',.2f') +print(formatted) + +b = 'my string' +formatted = format(b, '^20s') +print('*', formatted, '*') + + +# Example 15 +key = 'my_var' +value = 1.234 + +formatted = '{} = {}'.format(key, value) +print(formatted) + + +# Example 16 +formatted = '{:<10} = {:.2f}'.format(key, value) +print(formatted) + + +# Example 17 +print('%.2f%%' % 12.5) +print('{} replaces {{}}'.format(1.23)) + + +# Example 18 +formatted = '{1} = {0}'.format(key, value) +print(formatted) + + +# Example 19 +formatted = '{0} loves food. See {0} cook.'.format(name) +print(formatted) + + +# Example 20 +for i, (item, count) in enumerate(pantry): + old_style = '#%d: %-10s = %d' % ( + i + 1, + item.title(), + round(count)) + + new_style = '#{}: {:<10s} = {}'.format( + i + 1, + item.title(), + round(count)) + + assert old_style == new_style + + +# Example 21 +formatted = 'First letter is {menu[oyster][0]!r}'.format( + menu=menu) +print(formatted) + + +# Example 22 +old_template = ( + 'Today\'s soup is %(soup)s, ' + 'buy one get two %(oyster)s oysters, ' + 'and our special entrée is %(special)s.') +old_formatted = template % { + 'soup': 'lentil', + 'oyster': 'kumamoto', + 'special': 'schnitzel', +} + +new_template = ( + 'Today\'s soup is {soup}, ' + 'buy one get two {oyster} oysters, ' + 'and our special entrée is {special}.') +new_formatted = new_template.format( + soup='lentil', + oyster='kumamoto', + special='schnitzel', +) + +assert old_formatted == new_formatted + + +# Example 23 +key = 'my_var' +value = 1.234 + +formatted = f'{key} = {value}' +print(formatted) + + +# Example 24 +formatted = f'{key!r:<10} = {value:.2f}' +print(formatted) + + +# Example 25 +f_string = f'{key:<10} = {value:.2f}' + +c_tuple = '%-10s = %.2f' % (key, value) + +str_args = '{:<10} = {:.2f}'.format(key, value) + +str_kw = '{key:<10} = {value:.2f}'.format(key=key, value=value) + +c_dict = '%(key)-10s = %(value).2f' % {'key': key, 'value': value} + +assert c_tuple == c_dict == f_string +assert str_args == str_kw == f_string + + +# Example 26 +for i, (item, count) in enumerate(pantry): + old_style = '#%d: %-10s = %d' % ( + i + 1, + item.title(), + round(count)) + + new_style = '#{}: {:<10s} = {}'.format( + i + 1, + item.title(), + round(count)) + + f_string = f'#{i+1}: {item.title():<10s} = {round(count)}' + + assert old_style == new_style == f_string + + +# Example 27 +for i, (item, count) in enumerate(pantry): + print(f'#{i+1}: ' + f'{item.title():<10s} = ' + f'{round(count)}') + + +# Example 28 +places = 3 +number = 1.23456 +print(f'My number is {number:.{places}f}') diff --git a/example_code/item_05.py b/example_code/item_05.py new file mode 100755 index 0000000..ec407db --- /dev/null +++ b/example_code/item_05.py @@ -0,0 +1,113 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +from urllib.parse import parse_qs + +my_values = parse_qs('red=5&blue=0&green=', + keep_blank_values=True) +print(repr(my_values)) + + +# Example 2 +print('Red: ', my_values.get('red')) +print('Green: ', my_values.get('green')) +print('Opacity: ', my_values.get('opacity')) + + +# Example 3 +# For query string 'red=5&blue=0&green=' +red = my_values.get('red', [''])[0] or 0 +green = my_values.get('green', [''])[0] or 0 +opacity = my_values.get('opacity', [''])[0] or 0 +print(f'Red: {red!r}') +print(f'Green: {green!r}') +print(f'Opacity: {opacity!r}') + + +# Example 4 +red = int(my_values.get('red', [''])[0] or 0) +green = int(my_values.get('green', [''])[0] or 0) +opacity = int(my_values.get('opacity', [''])[0] or 0) +print(f'Red: {red!r}') +print(f'Green: {green!r}') +print(f'Opacity: {opacity!r}') + + +# Example 5 +red_str = my_values.get('red', ['']) +red = int(red_str[0]) if red_str[0] else 0 +green_str = my_values.get('green', ['']) +green = int(green_str[0]) if green_str[0] else 0 +opacity_str = my_values.get('opacity', ['']) +opacity = int(opacity_str[0]) if opacity_str[0] else 0 +print(f'Red: {red!r}') +print(f'Green: {green!r}') +print(f'Opacity: {opacity!r}') + + +# Example 6 +green_str = my_values.get('green', ['']) +if green_str[0]: + green = int(green_str[0]) +else: + green = 0 +print(f'Green: {green!r}') + + +# Example 7 +def get_first_int(values, key, default=0): + found = values.get(key, ['']) + if found[0]: + return int(found[0]) + return default + + +# Example 8 +green = get_first_int(my_values, 'green') +print(f'Green: {green!r}') diff --git a/example_code/item_06.py b/example_code/item_06.py new file mode 100755 index 0000000..beafb39 --- /dev/null +++ b/example_code/item_06.py @@ -0,0 +1,135 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +snack_calories = { + 'chips': 140, + 'popcorn': 80, + 'nuts': 190, +} +items = tuple(snack_calories.items()) +print(items) + + +# Example 2 +item = ('Peanut butter', 'Jelly') +first = item[0] +second = item[1] +print(first, 'and', second) + + +# Example 3 +try: + pair = ('Chocolate', 'Peanut butter') + pair[0] = 'Honey' +except: + logging.exception('Expected') +else: + assert False + + +# Example 4 +item = ('Peanut butter', 'Jelly') +first, second = item # Unpacking +print(first, 'and', second) + + +# Example 5 +favorite_snacks = { + 'salty': ('pretzels', 100), + 'sweet': ('cookies', 180), + 'veggie': ('carrots', 20), +} + +((type1, (name1, cals1)), + (type2, (name2, cals2)), + (type3, (name3, cals3))) = favorite_snacks.items() + +print(f'Favorite {type1} is {name1} with {cals1} calories') +print(f'Favorite {type2} is {name2} with {cals2} calories') +print(f'Favorite {type3} is {name3} with {cals3} calories') + + +# Example 6 +def bubble_sort(a): + for _ in range(len(a)): + for i in range(1, len(a)): + if a[i] < a[i-1]: + temp = a[i] + a[i] = a[i-1] + a[i-1] = temp + +names = ['pretzels', 'carrots', 'arugula', 'bacon'] +bubble_sort(names) +print(names) + + +# Example 7 +def bubble_sort(a): + for _ in range(len(a)): + for i in range(1, len(a)): + if a[i] < a[i-1]: + a[i-1], a[i] = a[i], a[i-1] # Swap + +names = ['pretzels', 'carrots', 'arugula', 'bacon'] +bubble_sort(names) +print(names) + + +# Example 8 +snacks = [('bacon', 350), ('donut', 240), ('muffin', 190)] +for i in range(len(snacks)): + item = snacks[i] + name = item[0] + calories = item[1] + print(f'#{i+1}: {name} has {calories} calories') + + +# Example 9 +for rank, (name, calories) in enumerate(snacks, 1): + print(f'#{rank}: {name} has {calories} calories') diff --git a/example_code/item_07.py b/example_code/item_07.py new file mode 100755 index 0000000..c1f4239 --- /dev/null +++ b/example_code/item_07.py @@ -0,0 +1,85 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +from random import randint + +random_bits = 0 +for i in range(32): + if randint(0, 1): + random_bits |= 1 << i + +print(bin(random_bits)) + + +# Example 2 +flavor_list = ['vanilla', 'chocolate', 'pecan', 'strawberry'] +for flavor in flavor_list: + print(f'{flavor} is delicious') + + +# Example 3 +for i in range(len(flavor_list)): + flavor = flavor_list[i] + print(f'{i + 1}: {flavor}') + + +# Example 4 +it = enumerate(flavor_list) +print(next(it)) +print(next(it)) + + +# Example 5 +for i, flavor in enumerate(flavor_list): + print(f'{i + 1}: {flavor}') + + +# Example 6 +for i, flavor in enumerate(flavor_list, 1): + print(f'{i}: {flavor}') diff --git a/example_code/item_08.py b/example_code/item_08.py new file mode 100755 index 0000000..c22151c --- /dev/null +++ b/example_code/item_08.py @@ -0,0 +1,99 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +names = ['Cecilia', 'Lise', 'Marie'] +counts = [len(n) for n in names] +print(counts) + + +# Example 2 +longest_name = None +max_count = 0 + +for i in range(len(names)): + count = counts[i] + if count > max_count: + longest_name = names[i] + max_count = count + +print(longest_name) + + +# Example 3 +longest_name = None +max_count = 0 +for i, name in enumerate(names): + count = counts[i] + if count > max_count: + longest_name = name + max_count = count +assert longest_name == 'Cecilia' + + +# Example 4 +longest_name = None +max_count = 0 +for name, count in zip(names, counts): + if count > max_count: + longest_name = name + max_count = count +assert longest_name == 'Cecilia' + + +# Example 5 +names.append('Rosalind') +for name, count in zip(names, counts): + print(name) + + +# Example 6 +import itertools + +for name, count in itertools.zip_longest(names, counts): + print(f'{name}: {count}') diff --git a/example_code/item_09.py b/example_code/item_09.py new file mode 100755 index 0000000..d96d033 --- /dev/null +++ b/example_code/item_09.py @@ -0,0 +1,113 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +for i in range(3): + print('Loop', i) +else: + print('Else block!') + + +# Example 2 +for i in range(3): + print('Loop', i) + if i == 1: + break +else: + print('Else block!') + + +# Example 3 +for x in []: + print('Never runs') +else: + print('For Else block!') + + +# Example 4 +while False: + print('Never runs') +else: + print('While Else block!') + + +# Example 5 +a = 4 +b = 9 + +for i in range(2, min(a, b) + 1): + print('Testing', i) + if a % i == 0 and b % i == 0: + print('Not coprime') + break +else: + print('Coprime') + + +# Example 6 +def coprime(a, b): + for i in range(2, min(a, b) + 1): + if a % i == 0 and b % i == 0: + return False + return True + +assert coprime(4, 9) +assert not coprime(3, 6) + + +# Example 7 +def coprime_alternate(a, b): + is_coprime = True + for i in range(2, min(a, b) + 1): + if a % i == 0 and b % i == 0: + is_coprime = False + break + return is_coprime + +assert coprime_alternate(4, 9) +assert not coprime_alternate(3, 6) diff --git a/example_code/item_10.py b/example_code/item_10.py new file mode 100755 index 0000000..a106c38 --- /dev/null +++ b/example_code/item_10.py @@ -0,0 +1,242 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +fresh_fruit = { + 'apple': 10, + 'banana': 8, + 'lemon': 5, +} + + +# Example 2 +def make_lemonade(count): + print(f'Making {count} lemons into lemonade') + +def out_of_stock(): + print('Out of stock!') + +count = fresh_fruit.get('lemon', 0) +if count: + make_lemonade(count) +else: + out_of_stock() + + +# Example 3 +if count := fresh_fruit.get('lemon', 0): + make_lemonade(count) +else: + out_of_stock() + + +# Example 4 +def make_cider(count): + print(f'Making cider with {count} apples') + +count = fresh_fruit.get('apple', 0) +if count >= 4: + make_cider(count) +else: + out_of_stock() + + +# Example 5 +if (count := fresh_fruit.get('apple', 0)) >= 4: + make_cider(count) +else: + out_of_stock() + + +# Example 6 +def slice_bananas(count): + print(f'Slicing {count} bananas') + return count * 4 + +class OutOfBananas(Exception): + pass + +def make_smoothies(count): + print(f'Making a smoothies with {count} banana slices') + +pieces = 0 +count = fresh_fruit.get('banana', 0) +if count >= 2: + pieces = slice_bananas(count) + +try: + smoothies = make_smoothies(pieces) +except OutOfBananas: + out_of_stock() + + +# Example 7 +count = fresh_fruit.get('banana', 0) +if count >= 2: + pieces = slice_bananas(count) +else: + pieces = 0 + +try: + smoothies = make_smoothies(pieces) +except OutOfBananas: + out_of_stock() + + +# Example 8 +pieces = 0 +if (count := fresh_fruit.get('banana', 0)) >= 2: + pieces = slice_bananas(count) + +try: + smoothies = make_smoothies(pieces) +except OutOfBananas: + out_of_stock() + + +# Example 9 +if (count := fresh_fruit.get('banana', 0)) >= 2: + pieces = slice_bananas(count) +else: + pieces = 0 + +try: + smoothies = make_smoothies(pieces) +except OutOfBananas: + out_of_stock() + + +# Example 10 +count = fresh_fruit.get('banana', 0) +if count >= 2: + pieces = slice_bananas(count) + to_enjoy = make_smoothies(pieces) +else: + count = fresh_fruit.get('apple', 0) + if count >= 4: + to_enjoy = make_cider(count) + else: + count = fresh_fruit.get('lemon', 0) + if count: + to_enjoy = make_lemonade(count) + else: + to_enjoy = 'Nothing' + + +# Example 11 +if (count := fresh_fruit.get('banana', 0)) >= 2: + pieces = slice_bananas(count) + to_enjoy = make_smoothies(pieces) +elif (count := fresh_fruit.get('apple', 0)) >= 4: + to_enjoy = make_cider(count) +elif count := fresh_fruit.get('lemon', 0): + to_enjoy = make_lemonade(count) +else: + to_enjoy = 'Nothing' + + +# Example 12 +FRUIT_TO_PICK = [ + {'apple': 1, 'banana': 3}, + {'lemon': 2, 'lime': 5}, + {'orange': 3, 'melon': 2}, +] + +def pick_fruit(): + if FRUIT_TO_PICK: + return FRUIT_TO_PICK.pop(0) + else: + return [] + +def make_juice(fruit, count): + return [(fruit, count)] + +bottles = [] +fresh_fruit = pick_fruit() +while fresh_fruit: + for fruit, count in fresh_fruit.items(): + batch = make_juice(fruit, count) + bottles.extend(batch) + fresh_fruit = pick_fruit() + +print(bottles) + + +# Example 13 +FRUIT_TO_PICK = [ + {'apple': 1, 'banana': 3}, + {'lemon': 2, 'lime': 5}, + {'orange': 3, 'melon': 2}, +] + +bottles = [] +while True: # Loop + fresh_fruit = pick_fruit() + if not fresh_fruit: # And a half + break + for fruit, count in fresh_fruit.items(): + batch = make_juice(fruit, count) + bottles.extend(batch) + +print(bottles) + + +# Example 14 +FRUIT_TO_PICK = [ + {'apple': 1, 'banana': 3}, + {'lemon': 2, 'lime': 5}, + {'orange': 3, 'melon': 2}, +] + +bottles = [] +while fresh_fruit := pick_fruit(): + for fruit, count in fresh_fruit.items(): + batch = make_juice(fruit, count) + bottles.extend(batch) + +print(bottles) diff --git a/example_code/item_11.py b/example_code/item_11.py new file mode 100755 index 0000000..f502f34 --- /dev/null +++ b/example_code/item_11.py @@ -0,0 +1,131 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] +print('Middle two: ', a[3:5]) +print('All but ends:', a[1:7]) + + +# Example 2 +assert a[:5] == a[0:5] + + +# Example 3 +assert a[5:] == a[5:len(a)] + + +# Example 4 +print(a[:]) +print(a[:5]) +print(a[:-1]) +print(a[4:]) +print(a[-3:]) +print(a[2:5]) +print(a[2:-1]) +print(a[-3:-1]) + + +# Example 5 +a[:] # ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] +a[:5] # ['a', 'b', 'c', 'd', 'e'] +a[:-1] # ['a', 'b', 'c', 'd', 'e', 'f', 'g'] +a[4:] # ['e', 'f', 'g', 'h'] +a[-3:] # ['f', 'g', 'h'] +a[2:5] # ['c', 'd', 'e'] +a[2:-1] # ['c', 'd', 'e', 'f', 'g'] +a[-3:-1] # ['f', 'g'] + + +# Example 6 +first_twenty_items = a[:20] +last_twenty_items = a[-20:] + + +# Example 7 +try: + a[20] +except: + logging.exception('Expected') +else: + assert False + + +# Example 8 +b = a[3:] +print('Before: ', b) +b[1] = 99 +print('After: ', b) +print('No change:', a) + + +# Example 9 +print('Before ', a) +a[2:7] = [99, 22, 14] +print('After ', a) + + +# Example 10 +print('Before ', a) +a[2:3] = [47, 11] +print('After ', a) + + +# Example 11 +b = a[:] +assert b == a and b is not a + + +# Example 12 +b = a +print('Before a', a) +print('Before b', b) +a[:] = [101, 102, 103] +assert a is b # Still the same list object +print('After a ', a) # Now has different contents +print('After b ', b) # Same list, so same contents as a diff --git a/example_code/item_12.py b/example_code/item_12.py new file mode 100755 index 0000000..7744737 --- /dev/null +++ b/example_code/item_12.py @@ -0,0 +1,99 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +x = ['red', 'orange', 'yellow', 'green', 'blue', 'purple'] +odds = x[::2] +evens = x[1::2] +print(odds) +print(evens) + + +# Example 2 +x = b'mongoose' +y = x[::-1] +print(y) + + +# Example 3 +x = '寿司' +y = x[::-1] +print(y) + + +# Example 4 +try: + w = '寿司' + x = w.encode('utf-8') + y = x[::-1] + z = y.decode('utf-8') +except: + logging.exception('Expected') +else: + assert False + + +# Example 5 +x = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] +x[::2] # ['a', 'c', 'e', 'g'] +x[::-2] # ['h', 'f', 'd', 'b'] + + +# Example 6 +x[2::2] # ['c', 'e', 'g'] +x[-2::-2] # ['g', 'e', 'c', 'a'] +x[-2:2:-2] # ['g', 'e'] +x[2:2:-2] # [] + + +# Example 7 +y = x[::2] # ['a', 'c', 'e', 'g'] +z = y[1:-1] # ['c', 'e'] +print(x) +print(y) +print(z) diff --git a/example_code/item_13.py b/example_code/item_13.py new file mode 100755 index 0000000..d2b9349 --- /dev/null +++ b/example_code/item_13.py @@ -0,0 +1,147 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +try: + car_ages = [0, 9, 4, 8, 7, 20, 19, 1, 6, 15] + car_ages_descending = sorted(car_ages, reverse=True) + oldest, second_oldest = car_ages_descending +except: + logging.exception('Expected') +else: + assert False + + +# Example 2 +oldest = car_ages_descending[0] +second_oldest = car_ages_descending[1] +others = car_ages_descending[2:] +print(oldest, second_oldest, others) + + +# Example 3 +oldest, second_oldest, *others = car_ages_descending +print(oldest, second_oldest, others) + + +# Example 4 +oldest, *others, youngest = car_ages_descending +print(oldest, youngest, others) + +*others, second_youngest, youngest = car_ages_descending +print(youngest, second_youngest, others) + + +# Example 5 +try: + # This will not compile + source = """*others = car_ages_descending""" + eval(source) +except: + logging.exception('Expected') +else: + assert False + + +# Example 6 +try: + # This will not compile + source = """first, *middle, *second_middle, last = [1, 2, 3, 4]""" + eval(source) +except: + logging.exception('Expected') +else: + assert False + + +# Example 7 +car_inventory = { + 'Downtown': ('Silver Shadow', 'Pinto', 'DMC'), + 'Airport': ('Skyline', 'Viper', 'Gremlin', 'Nova'), +} + +((loc1, (best1, *rest1)), + (loc2, (best2, *rest2))) = car_inventory.items() + +print(f'Best at {loc1} is {best1}, {len(rest1)} others') +print(f'Best at {loc2} is {best2}, {len(rest2)} others') + + +# Example 8 +short_list = [1, 2] +first, second, *rest = short_list +print(first, second, rest) + + +# Example 9 +it = iter(range(1, 3)) +first, second = it +print(f'{first} and {second}') + + +# Example 10 +def generate_csv(): + yield ('Date', 'Make' , 'Model', 'Year', 'Price') + for i in range(100): + yield ('2019-03-25', 'Honda', 'Fit' , '2010', '$3400') + yield ('2019-03-26', 'Ford', 'F150' , '2008', '$2400') + + +# Example 11 +all_csv_rows = list(generate_csv()) +header = all_csv_rows[0] +rows = all_csv_rows[1:] +print('CSV Header:', header) +print('Row count: ', len(rows)) + + +# Example 12 +it = generate_csv() +header, *rows = it +print('CSV Header:', header) +print('Row count: ', len(rows)) diff --git a/example_code/item_14.py b/example_code/item_14.py new file mode 100755 index 0000000..271663a --- /dev/null +++ b/example_code/item_14.py @@ -0,0 +1,166 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +numbers = [93, 86, 11, 68, 70] +numbers.sort() +print(numbers) + + +# Example 2 +class Tool: + def __init__(self, name, weight): + self.name = name + self.weight = weight + + def __repr__(self): + return f'Tool({self.name!r}, {self.weight})' + +tools = [ + Tool('level', 3.5), + Tool('hammer', 1.25), + Tool('screwdriver', 0.5), + Tool('chisel', 0.25), +] + + +# Example 3 +try: + tools.sort() +except: + logging.exception('Expected') +else: + assert False + + +# Example 4 +print('Unsorted:', repr(tools)) +tools.sort(key=lambda x: x.name) +print('\nSorted: ', tools) + + +# Example 5 +tools.sort(key=lambda x: x.weight) +print('By weight:', tools) + + +# Example 6 +places = ['home', 'work', 'New York', 'Paris'] +places.sort() +print('Case sensitive: ', places) +places.sort(key=lambda x: x.lower()) +print('Case insensitive:', places) + + +# Example 7 +power_tools = [ + Tool('drill', 4), + Tool('circular saw', 5), + Tool('jackhammer', 40), + Tool('sander', 4), +] + + +# Example 8 +saw = (5, 'circular saw') +jackhammer = (40, 'jackhammer') +assert not (jackhammer < saw) # Matches expectations + + +# Example 9 +drill = (4, 'drill') +sander = (4, 'sander') +assert drill[0] == sander[0] # Same weight +assert drill[1] < sander[1] # Alphabetically less +assert drill < sander # Thus, drill comes first + + +# Example 10 +power_tools.sort(key=lambda x: (x.weight, x.name)) +print(power_tools) + + +# Example 11 +power_tools.sort(key=lambda x: (x.weight, x.name), + reverse=True) # Makes all criteria descending +print(power_tools) + + +# Example 12 +power_tools.sort(key=lambda x: (-x.weight, x.name)) +print(power_tools) + + +# Example 13 +try: + power_tools.sort(key=lambda x: (x.weight, -x.name), + reverse=True) +except: + logging.exception('Expected') +else: + assert False + + +# Example 14 +power_tools.sort(key=lambda x: x.name) # Name ascending + +power_tools.sort(key=lambda x: x.weight, # Weight descending + reverse=True) + +print(power_tools) + + +# Example 15 +power_tools.sort(key=lambda x: x.name) +print(power_tools) + + +# Example 16 +power_tools.sort(key=lambda x: x.weight, + reverse=True) +print(power_tools) diff --git a/example_code/item_15.py b/example_code/item_15.py new file mode 100755 index 0000000..ef1ab90 --- /dev/null +++ b/example_code/item_15.py @@ -0,0 +1,186 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 2 +baby_names = { + 'cat': 'kitten', + 'dog': 'puppy', +} +print(baby_names) + + +# Example 4 +print(list(baby_names.keys())) +print(list(baby_names.values())) +print(list(baby_names.items())) +print(baby_names.popitem()) # Last item inserted + + +# Example 6 +def my_func(**kwargs): + for key, value in kwargs.items(): + print(f'{key} = {value}') + +my_func(goose='gosling', kangaroo='joey') + + +# Example 8 +class MyClass: + def __init__(self): + self.alligator = 'hatchling' + self.elephant = 'calf' + +a = MyClass() +for key, value in a.__dict__.items(): + print(f'{key} = {value}') + + +# Example 9 +votes = { + 'otter': 1281, + 'polar bear': 587, + 'fox': 863, +} + + +# Example 10 +def populate_ranks(votes, ranks): + names = list(votes.keys()) + names.sort(key=votes.get, reverse=True) + for i, name in enumerate(names, 1): + ranks[name] = i + + +# Example 11 +def get_winner(ranks): + return next(iter(ranks)) + + +# Example 12 +ranks = {} +populate_ranks(votes, ranks) +print(ranks) +winner = get_winner(ranks) +print(winner) + + +# Example 13 +from collections.abc import MutableMapping + +class SortedDict(MutableMapping): + def __init__(self): + self.data = {} + + def __getitem__(self, key): + return self.data[key] + + def __setitem__(self, key, value): + self.data[key] = value + + def __delitem__(self, key): + del self.data[key] + + def __iter__(self): + keys = list(self.data.keys()) + keys.sort() + for key in keys: + yield key + + def __len__(self): + return len(self.data) + +my_dict = SortedDict() +my_dict['otter'] = 1 +my_dict['cheeta'] = 2 +my_dict['anteater'] = 3 +my_dict['deer'] = 4 + +assert my_dict['otter'] == 1 + +assert 'cheeta' in my_dict +del my_dict['cheeta'] +assert 'cheeta' not in my_dict + +expected = [('anteater', 3), ('deer', 4), ('otter', 1)] +assert list(my_dict.items()) == expected + +assert not isinstance(my_dict, dict) + + +# Example 14 +sorted_ranks = SortedDict() +populate_ranks(votes, sorted_ranks) +print(sorted_ranks.data) +winner = get_winner(sorted_ranks) +print(winner) + + +# Example 15 +def get_winner(ranks): + for name, rank in ranks.items(): + if rank == 1: + return name + +winner = get_winner(sorted_ranks) +print(winner) + + +# Example 16 +try: + def get_winner(ranks): + if not isinstance(ranks, dict): + raise TypeError('must provide a dict instance') + return next(iter(ranks)) + + assert get_winner(ranks) == 'otter' + + get_winner(sorted_ranks) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_15_example_01.py b/example_code/item_15_example_01.py new file mode 100755 index 0000000..a797385 --- /dev/null +++ b/example_code/item_15_example_01.py @@ -0,0 +1,25 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3.5 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +# Example 1 +# Python 3.5 +baby_names = { + 'cat': 'kitten', + 'dog': 'puppy', +} +print(baby_names) diff --git a/example_code/item_15_example_03.py b/example_code/item_15_example_03.py new file mode 100755 index 0000000..39c54b1 --- /dev/null +++ b/example_code/item_15_example_03.py @@ -0,0 +1,28 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3.5 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +# Example 3 +# Python 3.5 +baby_names = { + 'cat': 'kitten', + 'dog': 'puppy', +} +print(list(baby_names.keys())) +print(list(baby_names.values())) +print(list(baby_names.items())) +print(baby_names.popitem()) # Randomly chooses an item diff --git a/example_code/item_15_example_05.py b/example_code/item_15_example_05.py new file mode 100755 index 0000000..5280de0 --- /dev/null +++ b/example_code/item_15_example_05.py @@ -0,0 +1,25 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3.5 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +# Example 5 +# Python 3.5 +def my_func(**kwargs): + for key, value in kwargs.items(): + print('%s = %s' % (key, value)) + +my_func(goose='gosling', kangaroo='joey') diff --git a/example_code/item_15_example_07.py b/example_code/item_15_example_07.py new file mode 100755 index 0000000..7073d5c --- /dev/null +++ b/example_code/item_15_example_07.py @@ -0,0 +1,28 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3.5 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +# Example 7 +# Python 3.5 +class MyClass: + def __init__(self): + self.alligator = 'hatchling' + self.elephant = 'calf' + +a = MyClass() +for key, value in a.__dict__.items(): + print('%s = %s' % (key, value)) diff --git a/example_code/item_15_example_17.py b/example_code/item_15_example_17.py new file mode 100755 index 0000000..83fa498 --- /dev/null +++ b/example_code/item_15_example_17.py @@ -0,0 +1,68 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +# Example 17 +# Check types in this file with: python -m mypy + +from typing import Dict, MutableMapping + +def populate_ranks(votes: Dict[str, int], + ranks: Dict[str, int]) -> None: + names = list(votes.keys()) + names.sort(key=votes.get, reverse=True) + for i, name in enumerate(names, 1): + ranks[name] = i + +def get_winner(ranks: Dict[str, int]) -> str: + return next(iter(ranks)) + +from typing import Iterator, MutableMapping + +class SortedDict(MutableMapping[str, int]): + def __init__(self) -> None: + self.data: Dict[str, int] = {} + + def __getitem__(self, key: str) -> int: + return self.data[key] + + def __setitem__(self, key: str, value: int) -> None: + self.data[key] = value + + def __delitem__(self, key: str) -> None: + del self.data[key] + + def __iter__(self) -> Iterator[str]: + keys = list(self.data.keys()) + keys.sort() + for key in keys: + yield key + + def __len__(self) -> int: + return len(self.data) + +votes = { + 'otter': 1281, + 'polar bear': 587, + 'fox': 863, +} + +sorted_ranks = SortedDict() +populate_ranks(votes, sorted_ranks) +print(sorted_ranks.data) +winner = get_winner(sorted_ranks) +print(winner) diff --git a/example_code/item_16.py b/example_code/item_16.py new file mode 100755 index 0000000..cf616ad --- /dev/null +++ b/example_code/item_16.py @@ -0,0 +1,198 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +counters = { + 'pumpernickel': 2, + 'sourdough': 1, +} + + +# Example 2 +key = 'wheat' + +if key in counters: + count = counters[key] +else: + count = 0 + +counters[key] = count + 1 + +print(counters) + + +# Example 3 +key = 'brioche' + +try: + count = counters[key] +except KeyError: + count = 0 + +counters[key] = count + 1 + +print(counters) + + +# Example 4 +key = 'multigrain' + +count = counters.get(key, 0) +counters[key] = count + 1 + +print(counters) + + +# Example 5 +key = 'baguette' + +if key not in counters: + counters[key] = 0 +counters[key] += 1 + +key = 'ciabatta' + +if key in counters: + counters[key] += 1 +else: + counters[key] = 1 + +key = 'ciabatta' + +try: + counters[key] += 1 +except KeyError: + counters[key] = 1 + +print(counters) + + +# Example 6 +votes = { + 'baguette': ['Bob', 'Alice'], + 'ciabatta': ['Coco', 'Deb'], +} + +key = 'brioche' +who = 'Elmer' + +if key in votes: + names = votes[key] +else: + votes[key] = names = [] + +names.append(who) +print(votes) + + +# Example 7 +key = 'rye' +who = 'Felix' + +try: + names = votes[key] +except KeyError: + votes[key] = names = [] + +names.append(who) + +print(votes) + + +# Example 8 +key = 'wheat' +who = 'Gertrude' + +names = votes.get(key) +if names is None: + votes[key] = names = [] + +names.append(who) + +print(votes) + + +# Example 9 +key = 'brioche' +who = 'Hugh' + +if (names := votes.get(key)) is None: + votes[key] = names = [] + +names.append(who) + +print(votes) + + +# Example 10 +key = 'cornbread' +who = 'Kirk' + +names = votes.setdefault(key, []) +names.append(who) + +print(votes) + + +# Example 11 +data = {} +key = 'foo' +value = [] +data.setdefault(key, value) +print('Before:', data) +value.append('hello') +print('After: ', data) + + +# Example 12 +key = 'dutch crunch' + +count = counters.setdefault(key, 0) +counters[key] = count + 1 + +print(counters) diff --git a/example_code/item_17.py b/example_code/item_17.py new file mode 100755 index 0000000..b0e86ff --- /dev/null +++ b/example_code/item_17.py @@ -0,0 +1,99 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +visits = { + 'Mexico': {'Tulum', 'Puerto Vallarta'}, + 'Japan': {'Hakone'}, +} + + +# Example 2 +visits.setdefault('France', set()).add('Arles') # Short + +if (japan := visits.get('Japan')) is None: # Long + visits['Japan'] = japan = set() +japan.add('Kyoto') +original_print = print +print = pprint + +print(visits) +print = original_print + + +# Example 3 +class Visits: + def __init__(self): + self.data = {} + + def add(self, country, city): + city_set = self.data.setdefault(country, set()) + city_set.add(city) + + +# Example 4 +visits = Visits() +visits.add('Russia', 'Yekaterinburg') +visits.add('Tanzania', 'Zanzibar') +print(visits.data) + + +# Example 5 +from collections import defaultdict + +class Visits: + def __init__(self): + self.data = defaultdict(set) + + def add(self, country, city): + self.data[country].add(city) + +visits = Visits() +visits.add('England', 'Bath') +visits.add('England', 'London') +print(visits.data) diff --git a/example_code/item_18.py b/example_code/item_18.py new file mode 100755 index 0000000..2d9814f --- /dev/null +++ b/example_code/item_18.py @@ -0,0 +1,191 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +pictures = {} +path = 'profile_1234.png' + +with open(path, 'wb') as f: + f.write(b'image data here 1234') + +if (handle := pictures.get(path)) is None: + try: + handle = open(path, 'a+b') + except OSError: + print(f'Failed to open path {path}') + raise + else: + pictures[path] = handle + +handle.seek(0) +image_data = handle.read() + +print(pictures) +print(image_data) + + +# Example 2 +# Examples using in and KeyError +pictures = {} +path = 'profile_9991.png' + +with open(path, 'wb') as f: + f.write(b'image data here 9991') + +if path in pictures: + handle = pictures[path] +else: + try: + handle = open(path, 'a+b') + except OSError: + print(f'Failed to open path {path}') + raise + else: + pictures[path] = handle + +handle.seek(0) +image_data = handle.read() + +print(pictures) +print(image_data) + +pictures = {} +path = 'profile_9922.png' + +with open(path, 'wb') as f: + f.write(b'image data here 9991') + +try: + handle = pictures[path] +except KeyError: + try: + handle = open(path, 'a+b') + except OSError: + print(f'Failed to open path {path}') + raise + else: + pictures[path] = handle + +handle.seek(0) +image_data = handle.read() + +print(pictures) +print(image_data) + + +# Example 3 +pictures = {} +path = 'profile_9239.png' + +with open(path, 'wb') as f: + f.write(b'image data here 9239') + +try: + handle = pictures.setdefault(path, open(path, 'a+b')) +except OSError: + print(f'Failed to open path {path}') + raise +else: + handle.seek(0) + image_data = handle.read() + +print(pictures) +print(image_data) + + +# Example 4 +try: + path = 'profile_4555.csv' + + with open(path, 'wb') as f: + f.write(b'image data here 9239') + + from collections import defaultdict + + def open_picture(profile_path): + try: + return open(profile_path, 'a+b') + except OSError: + print(f'Failed to open path {profile_path}') + raise + + pictures = defaultdict(open_picture) + handle = pictures[path] + handle.seek(0) + image_data = handle.read() +except: + logging.exception('Expected') +else: + assert False + + +# Example 5 +path = 'account_9090.csv' + +with open(path, 'wb') as f: + f.write(b'image data here 9090') + +def open_picture(profile_path): + try: + return open(profile_path, 'a+b') + except OSError: + print(f'Failed to open path {profile_path}') + raise + +class Pictures(dict): + def __missing__(self, key): + value = open_picture(key) + self[key] = value + return value + +pictures = Pictures() +handle = pictures[path] +handle.seek(0) +image_data = handle.read() +print(pictures) +print(image_data) diff --git a/example_code/item_19.py b/example_code/item_19.py new file mode 100755 index 0000000..35f20c7 --- /dev/null +++ b/example_code/item_19.py @@ -0,0 +1,142 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +def get_stats(numbers): + minimum = min(numbers) + maximum = max(numbers) + return minimum, maximum + +lengths = [63, 73, 72, 60, 67, 66, 71, 61, 72, 70] + +minimum, maximum = get_stats(lengths) # Two return values + +print(f'Min: {minimum}, Max: {maximum}') + + +# Example 2 +first, second = 1, 2 +assert first == 1 +assert second == 2 + +def my_function(): + return 1, 2 + +first, second = my_function() +assert first == 1 +assert second == 2 + + +# Example 3 +def get_avg_ratio(numbers): + average = sum(numbers) / len(numbers) + scaled = [x / average for x in numbers] + scaled.sort(reverse=True) + return scaled + +longest, *middle, shortest = get_avg_ratio(lengths) + +print(f'Longest: {longest:>4.0%}') +print(f'Shortest: {shortest:>4.0%}') + + +# Example 4 +def get_stats(numbers): + minimum = min(numbers) + maximum = max(numbers) + count = len(numbers) + average = sum(numbers) / count + + sorted_numbers = sorted(numbers) + middle = count // 2 + if count % 2 == 0: + lower = sorted_numbers[middle - 1] + upper = sorted_numbers[middle] + median = (lower + upper) / 2 + else: + median = sorted_numbers[middle] + + return minimum, maximum, average, median, count + +minimum, maximum, average, median, count = get_stats(lengths) + +print(f'Min: {minimum}, Max: {maximum}') +print(f'Average: {average}, Median: {median}, Count {count}') + +assert minimum == 60 +assert maximum == 73 +assert average == 67.5 +assert median == 68.5 +assert count == 10 + +# Verify odd count median +_, _, _, median, count = get_stats([1, 2, 3]) +assert median == 2 +assert count == 3 + + +# Example 5 +# Correct: +minimum, maximum, average, median, count = get_stats(lengths) + +# Oops! Median and average swapped: +minimum, maximum, median, average, count = get_stats(lengths) + + +# Example 6 +minimum, maximum, average, median, count = get_stats( + lengths) + +minimum, maximum, average, median, count = \ + get_stats(lengths) + +(minimum, maximum, average, + median, count) = get_stats(lengths) + +(minimum, maximum, average, median, count + ) = get_stats(lengths) diff --git a/example_code/item_20.py b/example_code/item_20.py new file mode 100755 index 0000000..4a9639b --- /dev/null +++ b/example_code/item_20.py @@ -0,0 +1,143 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +def careful_divide(a, b): + try: + return a / b + except ZeroDivisionError: + return None + +assert careful_divide(4, 2) == 2 +assert careful_divide(0, 1) == 0 +assert careful_divide(3, 6) == 0.5 +assert careful_divide(1, 0) == None + + +# Example 2 +x, y = 1, 0 +result = careful_divide(x, y) +if result is None: + print('Invalid inputs') +else: + print('Result is %.1f' % result) + + +# Example 3 +x, y = 0, 5 +result = careful_divide(x, y) +if not result: + print('Invalid inputs') # This runs! But shouldn't +else: + assert False + + +# Example 4 +def careful_divide(a, b): + try: + return True, a / b + except ZeroDivisionError: + return False, None + +assert careful_divide(4, 2) == (True, 2) +assert careful_divide(0, 1) == (True, 0) +assert careful_divide(3, 6) == (True, 0.5) +assert careful_divide(1, 0) == (False, None) + + +# Example 5 +x, y = 5, 0 +success, result = careful_divide(x, y) +if not success: + print('Invalid inputs') + + +# Example 6 +x, y = 5, 0 +_, result = careful_divide(x, y) +if not result: + print('Invalid inputs') + + +# Example 7 +def careful_divide(a, b): + try: + return a / b + except ZeroDivisionError as e: + raise ValueError('Invalid inputs') + + +# Example 8 +x, y = 5, 2 +try: + result = careful_divide(x, y) +except ValueError: + print('Invalid inputs') +else: + print('Result is %.1f' % result) + + +# Example 9 +def careful_divide(a: float, b: float) -> float: + """Divides a by b. + + Raises: + ValueError: When the inputs cannot be divided. + """ + try: + return a / b + except ZeroDivisionError as e: + raise ValueError('Invalid inputs') + +try: + result = careful_divide(1, 0) + assert False +except ValueError: + pass # Expected + +assert careful_divide(1, 5) == 0.2 diff --git a/example_code/item_21.py b/example_code/item_21.py new file mode 100755 index 0000000..10f97ae --- /dev/null +++ b/example_code/item_21.py @@ -0,0 +1,141 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +def sort_priority(values, group): + def helper(x): + if x in group: + return (0, x) + return (1, x) + values.sort(key=helper) + + +# Example 2 +numbers = [8, 3, 1, 2, 5, 4, 7, 6] +group = {2, 3, 5, 7} +sort_priority(numbers, group) +print(numbers) + + +# Example 3 +def sort_priority2(numbers, group): + found = False + def helper(x): + if x in group: + found = True # Seems simple + return (0, x) + return (1, x) + numbers.sort(key=helper) + return found + + +# Example 4 +numbers = [8, 3, 1, 2, 5, 4, 7, 6] +found = sort_priority2(numbers, group) +print('Found:', found) +print(numbers) + + +# Example 5 +try: + foo = does_not_exist * 5 +except: + logging.exception('Expected') +else: + assert False + + +# Example 6 +def sort_priority2(numbers, group): + found = False # Scope: 'sort_priority2' + def helper(x): + if x in group: + found = True # Scope: 'helper' -- Bad! + return (0, x) + return (1, x) + numbers.sort(key=helper) + return found + + +# Example 7 +def sort_priority3(numbers, group): + found = False + def helper(x): + nonlocal found # Added + if x in group: + found = True + return (0, x) + return (1, x) + numbers.sort(key=helper) + return found + + +# Example 8 +numbers = [8, 3, 1, 2, 5, 4, 7, 6] +found = sort_priority3(numbers, group) +assert found +assert numbers == [2, 3, 5, 7, 1, 4, 6, 8] + + +# Example 9 +numbers = [8, 3, 1, 2, 5, 4, 7, 6] +class Sorter: + def __init__(self, group): + self.group = group + self.found = False + + def __call__(self, x): + if x in self.group: + self.found = True + return (0, x) + return (1, x) + +sorter = Sorter(group) +numbers.sort(key=sorter) +assert sorter.found is True +assert numbers == [2, 3, 5, 7, 1, 4, 6, 8] diff --git a/example_code/item_22.py b/example_code/item_22.py new file mode 100755 index 0000000..4b8abbd --- /dev/null +++ b/example_code/item_22.py @@ -0,0 +1,100 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +def log(message, values): + if not values: + print(message) + else: + values_str = ', '.join(str(x) for x in values) + print(f'{message}: {values_str}') + +log('My numbers are', [1, 2]) +log('Hi there', []) + + +# Example 2 +def log(message, *values): # The only difference + if not values: + print(message) + else: + values_str = ', '.join(str(x) for x in values) + print(f'{message}: {values_str}') + +log('My numbers are', 1, 2) +log('Hi there') # Much better + + +# Example 3 +favorites = [7, 33, 99] +log('Favorite colors', *favorites) + + +# Example 4 +def my_generator(): + for i in range(10): + yield i + +def my_func(*args): + print(args) + +it = my_generator() +my_func(*it) + + +# Example 5 +def log(sequence, message, *values): + if not values: + print(f'{sequence} - {message}') + else: + values_str = ', '.join(str(x) for x in values) + print(f'{sequence} - {message}: {values_str}') + +log(1, 'Favorites', 7, 33) # New with *args OK +log(1, 'Hi there') # New message only OK +log('Favorite numbers', 7, 33) # Old usage breaks diff --git a/example_code/item_23.py b/example_code/item_23.py new file mode 100755 index 0000000..99c6fe8 --- /dev/null +++ b/example_code/item_23.py @@ -0,0 +1,161 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +def remainder(number, divisor): + return number % divisor + +assert remainder(20, 7) == 6 + + +# Example 2 +remainder(20, 7) +remainder(20, divisor=7) +remainder(number=20, divisor=7) +remainder(divisor=7, number=20) + + +# Example 3 +try: + # This will not compile + source = """remainder(number=20, 7)""" + eval(source) +except: + logging.exception('Expected') +else: + assert False + + +# Example 4 +try: + remainder(20, number=7) +except: + logging.exception('Expected') +else: + assert False + + +# Example 5 +my_kwargs = { + 'number': 20, + 'divisor': 7, +} +assert remainder(**my_kwargs) == 6 + + +# Example 6 +my_kwargs = { + 'divisor': 7, +} +assert remainder(number=20, **my_kwargs) == 6 + + +# Example 7 +my_kwargs = { + 'number': 20, +} +other_kwargs = { + 'divisor': 7, +} +assert remainder(**my_kwargs, **other_kwargs) == 6 + + +# Example 8 +def print_parameters(**kwargs): + for key, value in kwargs.items(): + print(f'{key} = {value}') + +print_parameters(alpha=1.5, beta=9, gamma=4) + + +# Example 9 +def flow_rate(weight_diff, time_diff): + return weight_diff / time_diff + +weight_diff = 0.5 +time_diff = 3 +flow = flow_rate(weight_diff, time_diff) +print(f'{flow:.3} kg per second') + + +# Example 10 +def flow_rate(weight_diff, time_diff, period): + return (weight_diff / time_diff) * period + + +# Example 11 +flow_per_second = flow_rate(weight_diff, time_diff, 1) + + +# Example 12 +def flow_rate(weight_diff, time_diff, period=1): + return (weight_diff / time_diff) * period + + +# Example 13 +flow_per_second = flow_rate(weight_diff, time_diff) +flow_per_hour = flow_rate(weight_diff, time_diff, period=3600) +print(flow_per_second) +print(flow_per_hour) + + +# Example 14 +def flow_rate(weight_diff, time_diff, + period=1, units_per_kg=1): + return ((weight_diff * units_per_kg) / time_diff) * period + + +# Example 15 +pounds_per_hour = flow_rate(weight_diff, time_diff, + period=3600, units_per_kg=2.2) +print(pounds_per_hour) + + +# Example 16 +pounds_per_hour = flow_rate(weight_diff, time_diff, 3600, 2.2) +print(pounds_per_hour) diff --git a/example_code/item_24.py b/example_code/item_24.py new file mode 100755 index 0000000..124da9b --- /dev/null +++ b/example_code/item_24.py @@ -0,0 +1,128 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +from time import sleep +from datetime import datetime + +def log(message, when=datetime.now()): + print(f'{when}: {message}') + +log('Hi there!') +sleep(0.1) +log('Hello again!') + + +# Example 2 +def log(message, when=None): + """Log a message with a timestamp. + + Args: + message: Message to print. + when: datetime of when the message occurred. + Defaults to the present time. + """ + if when is None: + when = datetime.now() + print(f'{when}: {message}') + + +# Example 3 +log('Hi there!') +sleep(0.1) +log('Hello again!') + + +# Example 4 +import json + +def decode(data, default={}): + try: + return json.loads(data) + except ValueError: + return default + + +# Example 5 +foo = decode('bad data') +foo['stuff'] = 5 +bar = decode('also bad') +bar['meep'] = 1 +print('Foo:', foo) +print('Bar:', bar) + + +# Example 6 +assert foo is bar + + +# Example 7 +def decode(data, default=None): + """Load JSON data from a string. + + Args: + data: JSON data to decode. + default: Value to return if decoding fails. + Defaults to an empty dictionary. + """ + try: + return json.loads(data) + except ValueError: + if default is None: + default = {} + return default + + +# Example 8 +foo = decode('bad data') +foo['stuff'] = 5 +bar = decode('also bad') +bar['meep'] = 1 +print('Foo:', foo) +print('Bar:', bar) +assert foo is not bar diff --git a/example_code/item_24_example_09.py b/example_code/item_24_example_09.py new file mode 100755 index 0000000..7cbaa71 --- /dev/null +++ b/example_code/item_24_example_09.py @@ -0,0 +1,41 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +# Example 9 +# Check types in this file with: python -m mypy + +from datetime import datetime +from time import sleep +from typing import Optional + +def log_typed(message: str, + when: Optional[datetime]=None) -> None: + """Log a message with a timestamp. + + Args: + message: Message to print. + when: datetime of when the message occurred. + Defaults to the present time. + """ + if when is None: + when = datetime.now() + print(f'{when}: {message}') + +log_typed('Hi there!') +sleep(0.1) +log_typed('Hello again!') diff --git a/example_code/item_25.py b/example_code/item_25.py new file mode 100755 index 0000000..2f1cce2 --- /dev/null +++ b/example_code/item_25.py @@ -0,0 +1,238 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +def safe_division(number, divisor, + ignore_overflow, + ignore_zero_division): + try: + return number / divisor + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float('inf') + else: + raise + + +# Example 2 +result = safe_division(1.0, 10**500, True, False) +print(result) + + +# Example 3 +result = safe_division(1.0, 0, False, True) +print(result) + + +# Example 4 +def safe_division_b(number, divisor, + ignore_overflow=False, # Changed + ignore_zero_division=False): # Changed + try: + return number / divisor + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float('inf') + else: + raise + + +# Example 5 +result = safe_division_b(1.0, 10**500, ignore_overflow=True) +print(result) + +result = safe_division_b(1.0, 0, ignore_zero_division=True) +print(result) + + +# Example 6 +assert safe_division_b(1.0, 10**500, True, False) == 0 + + +# Example 7 +def safe_division_c(number, divisor, *, # Changed + ignore_overflow=False, + ignore_zero_division=False): + try: + return number / divisor + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float('inf') + else: + raise + + +# Example 8 +try: + safe_division_c(1.0, 10**500, True, False) +except: + logging.exception('Expected') +else: + assert False + + +# Example 9 +result = safe_division_c(1.0, 0, ignore_zero_division=True) +assert result == float('inf') + +try: + result = safe_division_c(1.0, 0) +except ZeroDivisionError: + pass # Expected +else: + assert False + + +# Example 10 +assert safe_division_c(number=2, divisor=5) == 0.4 +assert safe_division_c(divisor=5, number=2) == 0.4 +assert safe_division_c(2, divisor=5) == 0.4 + + +# Example 11 +def safe_division_c(numerator, denominator, *, # Changed + ignore_overflow=False, + ignore_zero_division=False): + try: + return numerator / denominator + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float('inf') + else: + raise + + +# Example 12 +try: + safe_division_c(number=2, divisor=5) +except: + logging.exception('Expected') +else: + assert False + + +# Example 13 +def safe_division_d(numerator, denominator, /, *, # Changed + ignore_overflow=False, + ignore_zero_division=False): + try: + return numerator / denominator + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float('inf') + else: + raise + + +# Example 14 +assert safe_division_d(2, 5) == 0.4 + + +# Example 15 +try: + safe_division_d(numerator=2, denominator=5) +except: + logging.exception('Expected') +else: + assert False + + +# Example 16 +def safe_division_e(numerator, denominator, /, + ndigits=10, *, # Changed + ignore_overflow=False, + ignore_zero_division=False): + try: + fraction = numerator / denominator # Changed + return round(fraction, ndigits) # Changed + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float('inf') + else: + raise + + +# Example 17 +result = safe_division_e(22, 7) +print(result) + +result = safe_division_e(22, 7, 5) +print(result) + +result = safe_division_e(22, 7, ndigits=2) +print(result) diff --git a/example_code/item_26.py b/example_code/item_26.py new file mode 100755 index 0000000..105b47b --- /dev/null +++ b/example_code/item_26.py @@ -0,0 +1,126 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +def trace(func): + def wrapper(*args, **kwargs): + result = func(*args, **kwargs) + print(f'{func.__name__}({args!r}, {kwargs!r}) ' + f'-> {result!r}') + return result + return wrapper + + +# Example 2 +@trace +def fibonacci(n): + """Return the n-th Fibonacci number""" + if n in (0, 1): + return n + return (fibonacci(n - 2) + fibonacci(n - 1)) + + +# Example 3 +def fibonacci(n): + """Return the n-th Fibonacci number""" + if n in (0, 1): + return n + return fibonacci(n - 2) + fibonacci(n - 1) + +fibonacci = trace(fibonacci) + + +# Example 4 +fibonacci(4) + + +# Example 5 +print(fibonacci) + + +# Example 6 +help(fibonacci) + + +# Example 7 +try: + import pickle + + pickle.dumps(fibonacci) +except: + logging.exception('Expected') +else: + assert False + + +# Example 8 +from functools import wraps + +def trace(func): + @wraps(func) + def wrapper(*args, **kwargs): + result = func(*args, **kwargs) + print(f'{func.__name__}({args!r}, {kwargs!r}) ' + f'-> {result!r}') + return result + return wrapper + +@trace +def fibonacci(n): + """Return the n-th Fibonacci number""" + if n in (0, 1): + return n + return fibonacci(n - 2) + fibonacci(n - 1) + + +# Example 9 +help(fibonacci) + + +# Example 10 +print(pickle.dumps(fibonacci)) diff --git a/example_code/item_27.py b/example_code/item_27.py new file mode 100755 index 0000000..c1a4847 --- /dev/null +++ b/example_code/item_27.py @@ -0,0 +1,90 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +squares = [] +for x in a: + squares.append(x**2) +print(squares) + + +# Example 2 +squares = [x**2 for x in a] # List comprehension +print(squares) + + +# Example 3 +alt = map(lambda x: x ** 2, a) +assert list(alt) == squares, f'{alt} {squares}' + + +# Example 4 +even_squares = [x**2 for x in a if x % 2 == 0] +print(even_squares) + + +# Example 5 +alt = map(lambda x: x**2, filter(lambda x: x % 2 == 0, a)) +assert even_squares == list(alt) + + +# Example 6 +even_squares_dict = {x: x**2 for x in a if x % 2 == 0} +threes_cubed_set = {x**3 for x in a if x % 3 == 0} +print(even_squares_dict) +print(threes_cubed_set) + + +# Example 7 +alt_dict = dict(map(lambda x: (x, x**2), + filter(lambda x: x % 2 == 0, a))) +alt_set = set(map(lambda x: x**3, + filter(lambda x: x % 3 == 0, a))) +assert even_squares_dict == alt_dict +assert threes_cubed_set == alt_set diff --git a/example_code/item_28.py b/example_code/item_28.py new file mode 100755 index 0000000..d1efe24 --- /dev/null +++ b/example_code/item_28.py @@ -0,0 +1,93 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] +flat = [x for row in matrix for x in row] +print(flat) + + +# Example 2 +squared = [[x**2 for x in row] for row in matrix] +print(squared) + + +# Example 3 +my_lists = [ + [[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]], +] +flat = [x for sublist1 in my_lists + for sublist2 in sublist1 + for x in sublist2] +print(flat) + + +# Example 4 +flat = [] +for sublist1 in my_lists: + for sublist2 in sublist1: + flat.extend(sublist2) +print(flat) + + +# Example 5 +a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +b = [x for x in a if x > 4 if x % 2 == 0] +c = [x for x in a if x > 4 and x % 2 == 0] +print(b) +print(c) +assert b and c +assert b == c + + +# Example 6 +matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] +filtered = [[x for x in row if x % 3 == 0] + for row in matrix if sum(row) >= 10] +print(filtered) diff --git a/example_code/item_29.py b/example_code/item_29.py new file mode 100755 index 0000000..207faf1 --- /dev/null +++ b/example_code/item_29.py @@ -0,0 +1,137 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +stock = { + 'nails': 125, + 'screws': 35, + 'wingnuts': 8, + 'washers': 24, +} + +order = ['screws', 'wingnuts', 'clips'] + +def get_batches(count, size): + return count // size + +result = {} +for name in order: + count = stock.get(name, 0) + batches = get_batches(count, 8) + if batches: + result[name] = batches + +print(result) + + +# Example 2 +found = {name: get_batches(stock.get(name, 0), 8) + for name in order + if get_batches(stock.get(name, 0), 8)} +print(found) + + +# Example 3 +has_bug = {name: get_batches(stock.get(name, 0), 4) + for name in order + if get_batches(stock.get(name, 0), 8)} + +print('Expected:', found) +print('Found: ', has_bug) + + +# Example 4 +found = {name: batches for name in order + if (batches := get_batches(stock.get(name, 0), 8))} +assert found == {'screws': 4, 'wingnuts': 1}, found + + +# Example 5 +try: + result = {name: (tenth := count // 10) + for name, count in stock.items() if tenth > 0} +except: + logging.exception('Expected') +else: + assert False + + +# Example 6 +result = {name: tenth for name, count in stock.items() + if (tenth := count // 10) > 0} +print(result) + + +# Example 7 +half = [(last := count // 2) for count in stock.values()] +print(f'Last item of {half} is {last}') + + +# Example 8 +for count in stock.values(): # Leaks loop variable + pass +print(f'Last item of {list(stock.values())} is {count}') + + +# Example 9 +try: + del count + half = [count // 2 for count in stock.values()] + print(half) # Works + print(count) # Exception because loop variable didn't leak +except: + logging.exception('Expected') +else: + assert False + + +# Example 10 +found = ((name, batches) for name in order + if (batches := get_batches(stock.get(name, 0), 8))) +print(next(found)) +print(next(found)) diff --git a/example_code/item_30.py b/example_code/item_30.py new file mode 100755 index 0000000..ba4fa9b --- /dev/null +++ b/example_code/item_30.py @@ -0,0 +1,113 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +def index_words(text): + result = [] + if text: + result.append(0) + for index, letter in enumerate(text): + if letter == ' ': + result.append(index + 1) + return result + + +# Example 2 +address = 'Four score and seven years ago...' +address = 'Four score and seven years ago our fathers brought forth on this continent a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal.' +result = index_words(address) +print(result[:10]) + + +# Example 3 +def index_words_iter(text): + if text: + yield 0 + for index, letter in enumerate(text): + if letter == ' ': + yield index + 1 + + +# Example 4 +it = index_words_iter(address) +print(next(it)) +print(next(it)) + + +# Example 5 +result = list(index_words_iter(address)) +print(result[:10]) + + +# Example 6 +def index_file(handle): + offset = 0 + for line in handle: + if line: + yield offset + for letter in line: + offset += 1 + if letter == ' ': + yield offset + + +# Example 7 +address_lines = """Four score and seven years +ago our fathers brought forth on this +continent a new nation, conceived in liberty, +and dedicated to the proposition that all men +are created equal.""" + +with open('address.txt', 'w') as f: + f.write(address_lines) + +import itertools +with open('address.txt', 'r') as f: + it = index_file(f) + results = itertools.islice(it, 0, 10) + print(list(results)) diff --git a/example_code/item_31.py b/example_code/item_31.py new file mode 100755 index 0000000..122d480 --- /dev/null +++ b/example_code/item_31.py @@ -0,0 +1,209 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +def normalize(numbers): + total = sum(numbers) + result = [] + for value in numbers: + percent = 100 * value / total + result.append(percent) + return result + + +# Example 2 +visits = [15, 35, 80] +percentages = normalize(visits) +print(percentages) +assert sum(percentages) == 100.0 + + +# Example 3 +path = 'my_numbers.txt' +with open(path, 'w') as f: + for i in (15, 35, 80): + f.write('%d\n' % i) + +def read_visits(data_path): + with open(data_path) as f: + for line in f: + yield int(line) + + +# Example 4 +it = read_visits('my_numbers.txt') +percentages = normalize(it) +print(percentages) + + +# Example 5 +it = read_visits('my_numbers.txt') +print(list(it)) +print(list(it)) # Already exhausted + + +# Example 6 +def normalize_copy(numbers): + numbers_copy = list(numbers) # Copy the iterator + total = sum(numbers_copy) + result = [] + for value in numbers_copy: + percent = 100 * value / total + result.append(percent) + return result + + +# Example 7 +it = read_visits('my_numbers.txt') +percentages = normalize_copy(it) +print(percentages) +assert sum(percentages) == 100.0 + + +# Example 8 +def normalize_func(get_iter): + total = sum(get_iter()) # New iterator + result = [] + for value in get_iter(): # New iterator + percent = 100 * value / total + result.append(percent) + return result + + +# Example 9 +path = 'my_numbers.txt' +percentages = normalize_func(lambda: read_visits(path)) +print(percentages) +assert sum(percentages) == 100.0 + + +# Example 10 +class ReadVisits: + def __init__(self, data_path): + self.data_path = data_path + + def __iter__(self): + with open(self.data_path) as f: + for line in f: + yield int(line) + + +# Example 11 +visits = ReadVisits(path) +percentages = normalize(visits) +print(percentages) +assert sum(percentages) == 100.0 + + +# Example 12 +def normalize_defensive(numbers): + if iter(numbers) is numbers: # An iterator -- bad! + raise TypeError('Must supply a container') + total = sum(numbers) + result = [] + for value in numbers: + percent = 100 * value / total + result.append(percent) + return result + +visits = [15, 35, 80] +normalize_defensive(visits) # No error + +it = iter(visits) +try: + normalize_defensive(it) +except TypeError: + pass +else: + assert False + + +# Example 13 +from collections.abc import Iterator + +def normalize_defensive(numbers): + if isinstance(numbers, Iterator): # Another way to check + raise TypeError('Must supply a container') + total = sum(numbers) + result = [] + for value in numbers: + percent = 100 * value / total + result.append(percent) + return result + +visits = [15, 35, 80] +normalize_defensive(visits) # No error + +it = iter(visits) +try: + normalize_defensive(it) +except TypeError: + pass +else: + assert False + + +# Example 14 +visits = [15, 35, 80] +percentages = normalize_defensive(visits) +assert sum(percentages) == 100.0 + +visits = ReadVisits(path) +percentages = normalize_defensive(visits) +assert sum(percentages) == 100.0 + + +# Example 15 +try: + visits = [15, 35, 80] + it = iter(visits) + normalize_defensive(it) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_32.py b/example_code/item_32.py new file mode 100755 index 0000000..1cefe9b --- /dev/null +++ b/example_code/item_32.py @@ -0,0 +1,76 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +import random + +with open('my_file.txt', 'w') as f: + for _ in range(10): + f.write('a' * random.randint(0, 100)) + f.write('\n') + +value = [len(x) for x in open('my_file.txt')] +print(value) + + +# Example 2 +it = (len(x) for x in open('my_file.txt')) +print(it) + + +# Example 3 +print(next(it)) +print(next(it)) + + +# Example 4 +roots = ((x, x**0.5) for x in it) + + +# Example 5 +print(next(roots)) diff --git a/example_code/item_33.py b/example_code/item_33.py new file mode 100755 index 0000000..d7bd5c6 --- /dev/null +++ b/example_code/item_33.py @@ -0,0 +1,117 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +def move(period, speed): + for _ in range(period): + yield speed + +def pause(delay): + for _ in range(delay): + yield 0 + + +# Example 2 +def animate(): + for delta in move(4, 5.0): + yield delta + for delta in pause(3): + yield delta + for delta in move(2, 3.0): + yield delta + + +# Example 3 +def render(delta): + print(f'Delta: {delta:.1f}') + # Move the images onscreen + +def run(func): + for delta in func(): + render(delta) + +run(animate) + + +# Example 4 +def animate_composed(): + yield from move(4, 5.0) + yield from pause(3) + yield from move(2, 3.0) + +run(animate_composed) + + +# Example 5 +import timeit + +def child(): + for i in range(1_000_000): + yield i + +def slow(): + for i in child(): + yield i + +def fast(): + yield from child() + +baseline = timeit.timeit( + stmt='for _ in slow(): pass', + globals=globals(), + number=50) +print(f'Manual nesting {baseline:.2f}s') + +comparison = timeit.timeit( + stmt='for _ in fast(): pass', + globals=globals(), + number=50) +print(f'Composed nesting {comparison:.2f}s') + +reduction = -(comparison - baseline) / baseline +print(f'{reduction:.1%} less time') diff --git a/example_code/item_34.py b/example_code/item_34.py new file mode 100755 index 0000000..c4c19d9 --- /dev/null +++ b/example_code/item_34.py @@ -0,0 +1,171 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +import math + +def wave(amplitude, steps): + step_size = 2 * math.pi / steps + for step in range(steps): + radians = step * step_size + fraction = math.sin(radians) + output = amplitude * fraction + yield output + + +# Example 2 +def transmit(output): + if output is None: + print(f'Output is None') + else: + print(f'Output: {output:>5.1f}') + +def run(it): + for output in it: + transmit(output) + +run(wave(3.0, 8)) + + +# Example 3 +def my_generator(): + received = yield 1 + print(f'received = {received}') + +it = iter(my_generator()) +output = next(it) # Get first generator output +print(f'output = {output}') + +try: + next(it) # Run generator until it exits +except StopIteration: + pass +else: + assert False + + +# Example 4 +it = iter(my_generator()) +output = it.send(None) # Get first generator output +print(f'output = {output}') + +try: + it.send('hello!') # Send value into the generator +except StopIteration: + pass +else: + assert False + + +# Example 5 +def wave_modulating(steps): + step_size = 2 * math.pi / steps + amplitude = yield # Receive initial amplitude + for step in range(steps): + radians = step * step_size + fraction = math.sin(radians) + output = amplitude * fraction + amplitude = yield output # Receive next amplitude + + +# Example 6 +def run_modulating(it): + amplitudes = [ + None, 7, 7, 7, 2, 2, 2, 2, 10, 10, 10, 10, 10] + for amplitude in amplitudes: + output = it.send(amplitude) + transmit(output) + +run_modulating(wave_modulating(12)) + + +# Example 7 +def complex_wave(): + yield from wave(7.0, 3) + yield from wave(2.0, 4) + yield from wave(10.0, 5) + +run(complex_wave()) + + +# Example 8 +def complex_wave_modulating(): + yield from wave_modulating(3) + yield from wave_modulating(4) + yield from wave_modulating(5) + +run_modulating(complex_wave_modulating()) + + +# Example 9 +def wave_cascading(amplitude_it, steps): + step_size = 2 * math.pi / steps + for step in range(steps): + radians = step * step_size + fraction = math.sin(radians) + amplitude = next(amplitude_it) # Get next input + output = amplitude * fraction + yield output + + +# Example 10 +def complex_wave_cascading(amplitude_it): + yield from wave_cascading(amplitude_it, 3) + yield from wave_cascading(amplitude_it, 4) + yield from wave_cascading(amplitude_it, 5) + + +# Example 11 +def run_cascading(): + amplitudes = [7, 7, 7, 2, 2, 2, 2, 10, 10, 10, 10, 10] + it = complex_wave_cascading(iter(amplitudes)) + for amplitude in amplitudes: + output = next(it) + transmit(output) + +run_cascading() diff --git a/example_code/item_35.py b/example_code/item_35.py new file mode 100755 index 0000000..1d53234 --- /dev/null +++ b/example_code/item_35.py @@ -0,0 +1,157 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +try: + class MyError(Exception): + pass + + def my_generator(): + yield 1 + yield 2 + yield 3 + + it = my_generator() + print(next(it)) # Yield 1 + print(next(it)) # Yield 2 + print(it.throw(MyError('test error'))) +except: + logging.exception('Expected') +else: + assert False + + +# Example 2 +def my_generator(): + yield 1 + + try: + yield 2 + except MyError: + print('Got MyError!') + else: + yield 3 + + yield 4 + +it = my_generator() +print(next(it)) # Yield 1 +print(next(it)) # Yield 2 +print(it.throw(MyError('test error'))) + + +# Example 3 +class Reset(Exception): + pass + +def timer(period): + current = period + while current: + current -= 1 + try: + yield current + except Reset: + current = period + + +# Example 4 +RESETS = [ + False, False, False, True, False, True, False, + False, False, False, False, False, False, False] + +def check_for_reset(): + # Poll for external event + return RESETS.pop(0) + +def announce(remaining): + print(f'{remaining} ticks remaining') + +def run(): + it = timer(4) + while True: + try: + if check_for_reset(): + current = it.throw(Reset()) + else: + current = next(it) + except StopIteration: + break + else: + announce(current) + +run() + + +# Example 5 +class Timer: + def __init__(self, period): + self.current = period + self.period = period + + def reset(self): + self.current = self.period + + def __iter__(self): + while self.current: + self.current -= 1 + yield self.current + + +# Example 6 +RESETS = [ + False, False, True, False, True, False, + False, False, False, False, False, False, False] + +def run(): + timer = Timer(4) + for current in timer: + if check_for_reset(): + timer.reset() + announce(current) + +run() diff --git a/example_code/item_36.py b/example_code/item_36.py new file mode 100755 index 0000000..5829639 --- /dev/null +++ b/example_code/item_36.py @@ -0,0 +1,162 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +import itertools + + +# Example 2 +it = itertools.chain([1, 2, 3], [4, 5, 6]) +print(list(it)) + + +# Example 3 +it = itertools.repeat('hello', 3) +print(list(it)) + + +# Example 4 +it = itertools.cycle([1, 2]) +result = [next(it) for _ in range (10)] +print(result) + + +# Example 5 +it1, it2, it3 = itertools.tee(['first', 'second'], 3) +print(list(it1)) +print(list(it2)) +print(list(it3)) + + +# Example 6 +keys = ['one', 'two', 'three'] +values = [1, 2] + +normal = list(zip(keys, values)) +print('zip: ', normal) + +it = itertools.zip_longest(keys, values, fillvalue='nope') +longest = list(it) +print('zip_longest:', longest) + + +# Example 7 +values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +first_five = itertools.islice(values, 5) +print('First five: ', list(first_five)) + +middle_odds = itertools.islice(values, 2, 8, 2) +print('Middle odds:', list(middle_odds)) + + +# Example 8 +values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +less_than_seven = lambda x: x < 7 +it = itertools.takewhile(less_than_seven, values) +print(list(it)) + + +# Example 9 +values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +less_than_seven = lambda x: x < 7 +it = itertools.dropwhile(less_than_seven, values) +print(list(it)) + + +# Example 10 +values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +evens = lambda x: x % 2 == 0 + +filter_result = filter(evens, values) +print('Filter: ', list(filter_result)) + +filter_false_result = itertools.filterfalse(evens, values) +print('Filter false:', list(filter_false_result)) + + +# Example 11 +values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +sum_reduce = itertools.accumulate(values) +print('Sum: ', list(sum_reduce)) + +def sum_modulo_20(first, second): + output = first + second + return output % 20 + +modulo_reduce = itertools.accumulate(values, sum_modulo_20) +print('Modulo:', list(modulo_reduce)) + + +# Example 12 +single = itertools.product([1, 2], repeat=2) +print('Single: ', list(single)) + +multiple = itertools.product([1, 2], ['a', 'b']) +print('Multiple:', list(multiple)) + + +# Example 13 +it = itertools.permutations([1, 2, 3, 4], 2) +original_print = print +print = pprint +print(list(it)) +print = original_print + + +# Example 14 +it = itertools.combinations([1, 2, 3, 4], 2) +print(list(it)) + + +# Example 15 +it = itertools.combinations_with_replacement([1, 2, 3, 4], 2) +original_print = print +print = pprint +print(list(it)) +print = original_print diff --git a/example_code/item_37.py b/example_code/item_37.py new file mode 100755 index 0000000..aa7585a --- /dev/null +++ b/example_code/item_37.py @@ -0,0 +1,230 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class SimpleGradebook: + def __init__(self): + self._grades = {} + + def add_student(self, name): + self._grades[name] = [] + + def report_grade(self, name, score): + self._grades[name].append(score) + + def average_grade(self, name): + grades = self._grades[name] + return sum(grades) / len(grades) + + +# Example 2 +book = SimpleGradebook() +book.add_student('Isaac Newton') +book.report_grade('Isaac Newton', 90) +book.report_grade('Isaac Newton', 95) +book.report_grade('Isaac Newton', 85) + +print(book.average_grade('Isaac Newton')) + + +# Example 3 +from collections import defaultdict + +class BySubjectGradebook: + def __init__(self): + self._grades = {} # Outer dict + + def add_student(self, name): + self._grades[name] = defaultdict(list) # Inner dict + + +# Example 4 + def report_grade(self, name, subject, grade): + by_subject = self._grades[name] + grade_list = by_subject[subject] + grade_list.append(grade) + + def average_grade(self, name): + by_subject = self._grades[name] + total, count = 0, 0 + for grades in by_subject.values(): + total += sum(grades) + count += len(grades) + return total / count + + +# Example 5 +book = BySubjectGradebook() +book.add_student('Albert Einstein') +book.report_grade('Albert Einstein', 'Math', 75) +book.report_grade('Albert Einstein', 'Math', 65) +book.report_grade('Albert Einstein', 'Gym', 90) +book.report_grade('Albert Einstein', 'Gym', 95) +print(book.average_grade('Albert Einstein')) + + +# Example 6 +class WeightedGradebook: + def __init__(self): + self._grades = {} + + def add_student(self, name): + self._grades[name] = defaultdict(list) + + def report_grade(self, name, subject, score, weight): + by_subject = self._grades[name] + grade_list = by_subject[subject] + grade_list.append((score, weight)) + + +# Example 7 + def average_grade(self, name): + by_subject = self._grades[name] + + score_sum, score_count = 0, 0 + for subject, scores in by_subject.items(): + subject_avg, total_weight = 0, 0 + for score, weight in scores: + subject_avg += score * weight + total_weight += weight + + score_sum += subject_avg / total_weight + score_count += 1 + + return score_sum / score_count + + +# Example 8 +book = WeightedGradebook() +book.add_student('Albert Einstein') +book.report_grade('Albert Einstein', 'Math', 75, 0.05) +book.report_grade('Albert Einstein', 'Math', 65, 0.15) +book.report_grade('Albert Einstein', 'Math', 70, 0.80) +book.report_grade('Albert Einstein', 'Gym', 100, 0.40) +book.report_grade('Albert Einstein', 'Gym', 85, 0.60) +print(book.average_grade('Albert Einstein')) + + +# Example 9 +grades = [] +grades.append((95, 0.45)) +grades.append((85, 0.55)) +total = sum(score * weight for score, weight in grades) +total_weight = sum(weight for _, weight in grades) +average_grade = total / total_weight +print(average_grade) + + +# Example 10 +grades = [] +grades.append((95, 0.45, 'Great job')) +grades.append((85, 0.55, 'Better next time')) +total = sum(score * weight for score, weight, _ in grades) +total_weight = sum(weight for _, weight, _ in grades) +average_grade = total / total_weight +print(average_grade) + + +# Example 11 +from collections import namedtuple + +Grade = namedtuple('Grade', ('score', 'weight')) + + +# Example 12 +class Subject: + def __init__(self): + self._grades = [] + + def report_grade(self, score, weight): + self._grades.append(Grade(score, weight)) + + def average_grade(self): + total, total_weight = 0, 0 + for grade in self._grades: + total += grade.score * grade.weight + total_weight += grade.weight + return total / total_weight + + +# Example 13 +class Student: + def __init__(self): + self._subjects = defaultdict(Subject) + + def get_subject(self, name): + return self._subjects[name] + + def average_grade(self): + total, count = 0, 0 + for subject in self._subjects.values(): + total += subject.average_grade() + count += 1 + return total / count + + +# Example 14 +class Gradebook: + def __init__(self): + self._students = defaultdict(Student) + + def get_student(self, name): + return self._students[name] + + +# Example 15 +book = Gradebook() +albert = book.get_student('Albert Einstein') +math = albert.get_subject('Math') +math.report_grade(75, 0.05) +math.report_grade(65, 0.15) +math.report_grade(70, 0.80) +gym = albert.get_subject('Gym') +gym.report_grade(100, 0.40) +gym.report_grade(85, 0.60) +print(albert.average_grade()) diff --git a/example_code/item_38.py b/example_code/item_38.py new file mode 100755 index 0000000..f64d30a --- /dev/null +++ b/example_code/item_38.py @@ -0,0 +1,138 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +names = ['Socrates', 'Archimedes', 'Plato', 'Aristotle'] +names.sort(key=len) +print(names) + + +# Example 2 +def log_missing(): + print('Key added') + return 0 + + +# Example 3 +from collections import defaultdict + +current = {'green': 12, 'blue': 3} +increments = [ + ('red', 5), + ('blue', 17), + ('orange', 9), +] +result = defaultdict(log_missing, current) +print('Before:', dict(result)) +for key, amount in increments: + result[key] += amount +print('After: ', dict(result)) + + +# Example 4 +def increment_with_report(current, increments): + added_count = 0 + + def missing(): + nonlocal added_count # Stateful closure + added_count += 1 + return 0 + + result = defaultdict(missing, current) + for key, amount in increments: + result[key] += amount + + return result, added_count + + +# Example 5 +result, count = increment_with_report(current, increments) +assert count == 2 +print(result) + + +# Example 6 +class CountMissing: + def __init__(self): + self.added = 0 + + def missing(self): + self.added += 1 + return 0 + + +# Example 7 +counter = CountMissing() +result = defaultdict(counter.missing, current) # Method ref +for key, amount in increments: + result[key] += amount +assert counter.added == 2 +print(result) + + +# Example 8 +class BetterCountMissing: + def __init__(self): + self.added = 0 + + def __call__(self): + self.added += 1 + return 0 + +counter = BetterCountMissing() +assert counter() == 0 +assert callable(counter) + + +# Example 9 +counter = BetterCountMissing() +result = defaultdict(counter, current) # Relies on __call__ +for key, amount in increments: + result[key] += amount +assert counter.added == 2 +print(result) diff --git a/example_code/item_39.py b/example_code/item_39.py new file mode 100755 index 0000000..60ae575 --- /dev/null +++ b/example_code/item_39.py @@ -0,0 +1,209 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class InputData: + def read(self): + raise NotImplementedError + + +# Example 2 +class PathInputData(InputData): + def __init__(self, path): + super().__init__() + self.path = path + + def read(self): + with open(self.path) as f: + return f.read() + + +# Example 3 +class Worker: + def __init__(self, input_data): + self.input_data = input_data + self.result = None + + def map(self): + raise NotImplementedError + + def reduce(self, other): + raise NotImplementedError + + +# Example 4 +class LineCountWorker(Worker): + def map(self): + data = self.input_data.read() + self.result = data.count('\n') + + def reduce(self, other): + self.result += other.result + + +# Example 5 +import os + +def generate_inputs(data_dir): + for name in os.listdir(data_dir): + yield PathInputData(os.path.join(data_dir, name)) + + +# Example 6 +def create_workers(input_list): + workers = [] + for input_data in input_list: + workers.append(LineCountWorker(input_data)) + return workers + + +# Example 7 +from threading import Thread + +def execute(workers): + threads = [Thread(target=w.map) for w in workers] + for thread in threads: thread.start() + for thread in threads: thread.join() + + first, *rest = workers + for worker in rest: + first.reduce(worker) + return first.result + + +# Example 8 +def mapreduce(data_dir): + inputs = generate_inputs(data_dir) + workers = create_workers(inputs) + return execute(workers) + + +# Example 9 +import os +import random + +def write_test_files(tmpdir): + os.makedirs(tmpdir) + for i in range(100): + with open(os.path.join(tmpdir, str(i)), 'w') as f: + f.write('\n' * random.randint(0, 100)) + +tmpdir = 'test_inputs' +write_test_files(tmpdir) + +result = mapreduce(tmpdir) +print(f'There are {result} lines') + + +# Example 10 +class GenericInputData: + def read(self): + raise NotImplementedError + + @classmethod + def generate_inputs(cls, config): + raise NotImplementedError + + +# Example 11 +class PathInputData(GenericInputData): + def __init__(self, path): + super().__init__() + self.path = path + + def read(self): + with open(self.path) as f: + return f.read() + + @classmethod + def generate_inputs(cls, config): + data_dir = config['data_dir'] + for name in os.listdir(data_dir): + yield cls(os.path.join(data_dir, name)) + + +# Example 12 +class GenericWorker: + def __init__(self, input_data): + self.input_data = input_data + self.result = None + + def map(self): + raise NotImplementedError + + def reduce(self, other): + raise NotImplementedError + + @classmethod + def create_workers(cls, input_class, config): + workers = [] + for input_data in input_class.generate_inputs(config): + workers.append(cls(input_data)) + return workers + + +# Example 13 +class LineCountWorker(GenericWorker): + def map(self): + data = self.input_data.read() + self.result = data.count('\n') + + def reduce(self, other): + self.result += other.result + + +# Example 14 +def mapreduce(worker_class, input_class, config): + workers = worker_class.create_workers(input_class, config) + return execute(workers) + + +# Example 15 +config = {'data_dir': tmpdir} +result = mapreduce(LineCountWorker, PathInputData, config) +print(f'There are {result} lines') diff --git a/example_code/item_40.py b/example_code/item_40.py new file mode 100755 index 0000000..136eeb0 --- /dev/null +++ b/example_code/item_40.py @@ -0,0 +1,174 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class MyBaseClass: + def __init__(self, value): + self.value = value + +class MyChildClass(MyBaseClass): + def __init__(self): + MyBaseClass.__init__(self, 5) + + def times_two(self): + return self.value * 2 + +foo = MyChildClass() +assert foo.times_two() == 10 + + +# Example 2 +class TimesTwo: + def __init__(self): + self.value *= 2 + +class PlusFive: + def __init__(self): + self.value += 5 + + +# Example 3 +class OneWay(MyBaseClass, TimesTwo, PlusFive): + def __init__(self, value): + MyBaseClass.__init__(self, value) + TimesTwo.__init__(self) + PlusFive.__init__(self) + + +# Example 4 +foo = OneWay(5) +print('First ordering value is (5 * 2) + 5 =', foo.value) + + +# Example 5 +class AnotherWay(MyBaseClass, PlusFive, TimesTwo): + def __init__(self, value): + MyBaseClass.__init__(self, value) + TimesTwo.__init__(self) + PlusFive.__init__(self) + + +# Example 6 +bar = AnotherWay(5) +print('Second ordering value is', bar.value) + + +# Example 7 +class TimesSeven(MyBaseClass): + def __init__(self, value): + MyBaseClass.__init__(self, value) + self.value *= 7 + +class PlusNine(MyBaseClass): + def __init__(self, value): + MyBaseClass.__init__(self, value) + self.value += 9 + + +# Example 8 +class ThisWay(TimesSeven, PlusNine): + def __init__(self, value): + TimesSeven.__init__(self, value) + PlusNine.__init__(self, value) + +foo = ThisWay(5) +print('Should be (5 * 7) + 9 = 44 but is', foo.value) + + +# Example 9 +class MyBaseClass: + def __init__(self, value): + self.value = value + +class TimesSevenCorrect(MyBaseClass): + def __init__(self, value): + super().__init__(value) + self.value *= 7 + +class PlusNineCorrect(MyBaseClass): + def __init__(self, value): + super().__init__(value) + self.value += 9 + + +# Example 10 +class GoodWay(TimesSevenCorrect, PlusNineCorrect): + def __init__(self, value): + super().__init__(value) + +foo = GoodWay(5) +print('Should be 7 * (5 + 9) = 98 and is', foo.value) + + +# Example 11 +mro_str = '\n'.join(repr(cls) for cls in GoodWay.mro()) +print(mro_str) + + +# Example 12 +class ExplicitTrisect(MyBaseClass): + def __init__(self, value): + super(ExplicitTrisect, self).__init__(value) + self.value /= 3 +assert ExplicitTrisect(9).value == 3 + + +# Example 13 +class AutomaticTrisect(MyBaseClass): + def __init__(self, value): + super(__class__, self).__init__(value) + self.value /= 3 + +class ImplicitTrisect(MyBaseClass): + def __init__(self, value): + super().__init__(value) + self.value /= 3 + +assert ExplicitTrisect(9).value == 3 +assert AutomaticTrisect(9).value == 3 +assert ImplicitTrisect(9).value == 3 diff --git a/example_code/item_41.py b/example_code/item_41.py new file mode 100755 index 0000000..ce8bfab --- /dev/null +++ b/example_code/item_41.py @@ -0,0 +1,177 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class ToDictMixin: + def to_dict(self): + return self._traverse_dict(self.__dict__) + + +# Example 2 + def _traverse_dict(self, instance_dict): + output = {} + for key, value in instance_dict.items(): + output[key] = self._traverse(key, value) + return output + + def _traverse(self, key, value): + if isinstance(value, ToDictMixin): + return value.to_dict() + elif isinstance(value, dict): + return self._traverse_dict(value) + elif isinstance(value, list): + return [self._traverse(key, i) for i in value] + elif hasattr(value, '__dict__'): + return self._traverse_dict(value.__dict__) + else: + return value + + +# Example 3 +class BinaryTree(ToDictMixin): + def __init__(self, value, left=None, right=None): + self.value = value + self.left = left + self.right = right + + +# Example 4 +tree = BinaryTree(10, + left=BinaryTree(7, right=BinaryTree(9)), + right=BinaryTree(13, left=BinaryTree(11))) +orig_print = print +print = pprint +print(tree.to_dict()) +print = orig_print + + +# Example 5 +class BinaryTreeWithParent(BinaryTree): + def __init__(self, value, left=None, + right=None, parent=None): + super().__init__(value, left=left, right=right) + self.parent = parent + + +# Example 6 + def _traverse(self, key, value): + if (isinstance(value, BinaryTreeWithParent) and + key == 'parent'): + return value.value # Prevent cycles + else: + return super()._traverse(key, value) + + +# Example 7 +root = BinaryTreeWithParent(10) +root.left = BinaryTreeWithParent(7, parent=root) +root.left.right = BinaryTreeWithParent(9, parent=root.left) +orig_print = print +print = pprint +print(root.to_dict()) +print = orig_print + + +# Example 8 +class NamedSubTree(ToDictMixin): + def __init__(self, name, tree_with_parent): + self.name = name + self.tree_with_parent = tree_with_parent + +my_tree = NamedSubTree('foobar', root.left.right) +orig_print = print +print = pprint +print(my_tree.to_dict()) # No infinite loop +print = orig_print + + +# Example 9 +import json + +class JsonMixin: + @classmethod + def from_json(cls, data): + kwargs = json.loads(data) + return cls(**kwargs) + + def to_json(self): + return json.dumps(self.to_dict()) + + +# Example 10 +class DatacenterRack(ToDictMixin, JsonMixin): + def __init__(self, switch=None, machines=None): + self.switch = Switch(**switch) + self.machines = [ + Machine(**kwargs) for kwargs in machines] + +class Switch(ToDictMixin, JsonMixin): + def __init__(self, ports=None, speed=None): + self.ports = ports + self.speed = speed + +class Machine(ToDictMixin, JsonMixin): + def __init__(self, cores=None, ram=None, disk=None): + self.cores = cores + self.ram = ram + self.disk = disk + + +# Example 11 +serialized = """{ + "switch": {"ports": 5, "speed": 1e9}, + "machines": [ + {"cores": 8, "ram": 32e9, "disk": 5e12}, + {"cores": 4, "ram": 16e9, "disk": 1e12}, + {"cores": 2, "ram": 4e9, "disk": 500e9} + ] +}""" + +deserialized = DatacenterRack.from_json(serialized) +roundtrip = deserialized.to_json() +assert json.loads(serialized) == json.loads(roundtrip) diff --git a/example_code/item_42.py b/example_code/item_42.py new file mode 100755 index 0000000..f6dd9fd --- /dev/null +++ b/example_code/item_42.py @@ -0,0 +1,214 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class MyObject: + def __init__(self): + self.public_field = 5 + self.__private_field = 10 + + def get_private_field(self): + return self.__private_field + + +# Example 2 +foo = MyObject() +assert foo.public_field == 5 + + +# Example 3 +assert foo.get_private_field() == 10 + + +# Example 4 +try: + foo.__private_field +except: + logging.exception('Expected') +else: + assert False + + +# Example 5 +class MyOtherObject: + def __init__(self): + self.__private_field = 71 + + @classmethod + def get_private_field_of_instance(cls, instance): + return instance.__private_field + +bar = MyOtherObject() +assert MyOtherObject.get_private_field_of_instance(bar) == 71 + + +# Example 6 +try: + class MyParentObject: + def __init__(self): + self.__private_field = 71 + + class MyChildObject(MyParentObject): + def get_private_field(self): + return self.__private_field + + baz = MyChildObject() + baz.get_private_field() +except: + logging.exception('Expected') +else: + assert False + + +# Example 7 +assert baz._MyParentObject__private_field == 71 + + +# Example 8 +print(baz.__dict__) + + +# Example 9 +class MyStringClass: + def __init__(self, value): + self.__value = value + + def get_value(self): + return str(self.__value) + +foo = MyStringClass(5) +assert foo.get_value() == '5' + + +# Example 10 +class MyIntegerSubclass(MyStringClass): + def get_value(self): + return int(self._MyStringClass__value) + +foo = MyIntegerSubclass('5') +assert foo.get_value() == 5 + + +# Example 11 +class MyBaseClass: + def __init__(self, value): + self.__value = value + + def get_value(self): + return self.__value + +class MyStringClass(MyBaseClass): + def get_value(self): + return str(super().get_value()) # Updated + +class MyIntegerSubclass(MyStringClass): + def get_value(self): + return int(self._MyStringClass__value) # Not updated + + +# Example 12 +try: + foo = MyIntegerSubclass(5) + foo.get_value() +except: + logging.exception('Expected') +else: + assert False + + +# Example 13 +class MyStringClass: + def __init__(self, value): + # This stores the user-supplied value for the object. + # It should be coercible to a string. Once assigned in + # the object it should be treated as immutable. + self._value = value + + + def get_value(self): + return str(self._value) +class MyIntegerSubclass(MyStringClass): + def get_value(self): + return self._value + +foo = MyIntegerSubclass(5) +assert foo.get_value() == 5 + + +# Example 14 +class ApiClass: + def __init__(self): + self._value = 5 + + def get(self): + return self._value + +class Child(ApiClass): + def __init__(self): + super().__init__() + self._value = 'hello' # Conflicts + +a = Child() +print(f'{a.get()} and {a._value} should be different') + + +# Example 15 +class ApiClass: + def __init__(self): + self.__value = 5 # Double underscore + + def get(self): + return self.__value # Double underscore + +class Child(ApiClass): + def __init__(self): + super().__init__() + self._value = 'hello' # OK! + +a = Child() +print(f'{a.get()} and {a._value} are different') diff --git a/example_code/item_43.py b/example_code/item_43.py new file mode 100755 index 0000000..5a842ac --- /dev/null +++ b/example_code/item_43.py @@ -0,0 +1,208 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class FrequencyList(list): + def __init__(self, members): + super().__init__(members) + + def frequency(self): + counts = {} + for item in self: + counts[item] = counts.get(item, 0) + 1 + return counts + + +# Example 2 +foo = FrequencyList(['a', 'b', 'a', 'c', 'b', 'a', 'd']) +print('Length is', len(foo)) +foo.pop() +print('After pop:', repr(foo)) +print('Frequency:', foo.frequency()) + + +# Example 3 +class BinaryNode: + def __init__(self, value, left=None, right=None): + self.value = value + self.left = left + self.right = right + + +# Example 4 +bar = [1, 2, 3] +bar[0] + + +# Example 5 +bar.__getitem__(0) + + +# Example 6 +class IndexableNode(BinaryNode): + def _traverse(self): + if self.left is not None: + yield from self.left._traverse() + yield self + if self.right is not None: + yield from self.right._traverse() + + def __getitem__(self, index): + for i, item in enumerate(self._traverse()): + if i == index: + return item.value + raise IndexError(f'Index {index} is out of range') + + +# Example 7 +tree = IndexableNode( + 10, + left=IndexableNode( + 5, + left=IndexableNode(2), + right=IndexableNode( + 6, + right=IndexableNode(7))), + right=IndexableNode( + 15, + left=IndexableNode(11))) + + +# Example 8 +print('LRR is', tree.left.right.right.value) +print('Index 0 is', tree[0]) +print('Index 1 is', tree[1]) +print('11 in the tree?', 11 in tree) +print('17 in the tree?', 17 in tree) +print('Tree is', list(tree)) + +try: + tree[100] +except IndexError: + pass +else: + assert False + + +# Example 9 +try: + len(tree) +except: + logging.exception('Expected') +else: + assert False + + +# Example 10 +class SequenceNode(IndexableNode): + def __len__(self): + for count, _ in enumerate(self._traverse(), 1): + pass + return count + + +# Example 11 +tree = SequenceNode( + 10, + left=SequenceNode( + 5, + left=SequenceNode(2), + right=SequenceNode( + 6, + right=SequenceNode(7))), + right=SequenceNode( + 15, + left=SequenceNode(11)) +) + +print('Tree length is', len(tree)) + + +# Example 12 +try: + # Make sure that this doesn't work + tree.count(4) +except: + logging.exception('Expected') +else: + assert False + + +# Example 13 +try: + from collections.abc import Sequence + + class BadType(Sequence): + pass + + foo = BadType() +except: + logging.exception('Expected') +else: + assert False + + +# Example 14 +class BetterNode(SequenceNode, Sequence): + pass + +tree = BetterNode( + 10, + left=BetterNode( + 5, + left=BetterNode(2), + right=BetterNode( + 6, + right=BetterNode(7))), + right=BetterNode( + 15, + left=BetterNode(11)) +) + +print('Index of 7 is', tree.index(7)) +print('Count of 10 is', tree.count(10)) diff --git a/example_code/item_44.py b/example_code/item_44.py new file mode 100755 index 0000000..a2062f5 --- /dev/null +++ b/example_code/item_44.py @@ -0,0 +1,192 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class OldResistor: + def __init__(self, ohms): + self._ohms = ohms + + def get_ohms(self): + return self._ohms + + def set_ohms(self, ohms): + self._ohms = ohms + + +# Example 2 +r0 = OldResistor(50e3) +print('Before:', r0.get_ohms()) +r0.set_ohms(10e3) +print('After: ', r0.get_ohms()) + + +# Example 3 +r0.set_ohms(r0.get_ohms() - 4e3) +assert r0.get_ohms() == 6e3 + + +# Example 4 +class Resistor: + def __init__(self, ohms): + self.ohms = ohms + self.voltage = 0 + self.current = 0 + +r1 = Resistor(50e3) +r1.ohms = 10e3 +print(f'{r1.ohms} ohms, ' + f'{r1.voltage} volts, ' + f'{r1.current} amps') + + +# Example 5 +r1.ohms += 5e3 + + +# Example 6 +class VoltageResistance(Resistor): + def __init__(self, ohms): + super().__init__(ohms) + self._voltage = 0 + + @property + def voltage(self): + return self._voltage + + @voltage.setter + def voltage(self, voltage): + self._voltage = voltage + self.current = self._voltage / self.ohms + + +# Example 7 +r2 = VoltageResistance(1e3) +print(f'Before: {r2.current:.2f} amps') +r2.voltage = 10 +print(f'After: {r2.current:.2f} amps') + + +# Example 8 +class BoundedResistance(Resistor): + def __init__(self, ohms): + super().__init__(ohms) + + @property + def ohms(self): + return self._ohms + + @ohms.setter + def ohms(self, ohms): + if ohms <= 0: + raise ValueError(f'ohms must be > 0; got {ohms}') + self._ohms = ohms + + +# Example 9 +try: + r3 = BoundedResistance(1e3) + r3.ohms = 0 +except: + logging.exception('Expected') +else: + assert False + + +# Example 10 +try: + BoundedResistance(-5) +except: + logging.exception('Expected') +else: + assert False + + +# Example 11 +class FixedResistance(Resistor): + def __init__(self, ohms): + super().__init__(ohms) + + @property + def ohms(self): + return self._ohms + + @ohms.setter + def ohms(self, ohms): + if hasattr(self, '_ohms'): + raise AttributeError("Ohms is immutable") + self._ohms = ohms + + +# Example 12 +try: + r4 = FixedResistance(1e3) + r4.ohms = 2e3 +except: + logging.exception('Expected') +else: + assert False + + +# Example 13 +class MysteriousResistor(Resistor): + @property + def ohms(self): + self.voltage = self._ohms * self.current + return self._ohms + + @ohms.setter + def ohms(self, ohms): + self._ohms = ohms + + +# Example 14 +r7 = MysteriousResistor(10) +r7.current = 0.01 +print(f'Before: {r7.voltage:.2f}') +r7.ohms +print(f'After: {r7.voltage:.2f}') diff --git a/example_code/item_45.py b/example_code/item_45.py new file mode 100755 index 0000000..d627894 --- /dev/null +++ b/example_code/item_45.py @@ -0,0 +1,162 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +from datetime import datetime, timedelta + +class Bucket: + def __init__(self, period): + self.period_delta = timedelta(seconds=period) + self.reset_time = datetime.now() + self.quota = 0 + + def __repr__(self): + return f'Bucket(quota={self.quota})' + +bucket = Bucket(60) +print(bucket) + + +# Example 2 +def fill(bucket, amount): + now = datetime.now() + if (now - bucket.reset_time) > bucket.period_delta: + bucket.quota = 0 + bucket.reset_time = now + bucket.quota += amount + + +# Example 3 +def deduct(bucket, amount): + now = datetime.now() + if (now - bucket.reset_time) > bucket.period_delta: + return False # Bucket hasn't been filled this period + if bucket.quota - amount < 0: + return False # Bucket was filled, but not enough + bucket.quota -= amount + return True # Bucket had enough, quota consumed + + +# Example 4 +bucket = Bucket(60) +fill(bucket, 100) +print(bucket) + + +# Example 5 +if deduct(bucket, 99): + print('Had 99 quota') +else: + print('Not enough for 99 quota') +print(bucket) + + +# Example 6 +if deduct(bucket, 3): + print('Had 3 quota') +else: + print('Not enough for 3 quota') +print(bucket) + + +# Example 7 +class NewBucket: + def __init__(self, period): + self.period_delta = timedelta(seconds=period) + self.reset_time = datetime.now() + self.max_quota = 0 + self.quota_consumed = 0 + + def __repr__(self): + return (f'NewBucket(max_quota={self.max_quota}, ' + f'quota_consumed={self.quota_consumed})') + + +# Example 8 + @property + def quota(self): + return self.max_quota - self.quota_consumed + + +# Example 9 + @quota.setter + def quota(self, amount): + delta = self.max_quota - amount + if amount == 0: + # Quota being reset for a new period + self.quota_consumed = 0 + self.max_quota = 0 + elif delta < 0: + # Quota being filled for the new period + assert self.quota_consumed == 0 + self.max_quota = amount + else: + # Quota being consumed during the period + assert self.max_quota >= self.quota_consumed + self.quota_consumed += delta + + +# Example 10 +bucket = NewBucket(60) +print('Initial', bucket) +fill(bucket, 100) +print('Filled', bucket) + +if deduct(bucket, 99): + print('Had 99 quota') +else: + print('Not enough for 99 quota') + +print('Now', bucket) + +if deduct(bucket, 3): + print('Had 3 quota') +else: + print('Not enough for 3 quota') + +print('Still', bucket) diff --git a/example_code/item_46.py b/example_code/item_46.py new file mode 100755 index 0000000..fd44042 --- /dev/null +++ b/example_code/item_46.py @@ -0,0 +1,227 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class Homework: + def __init__(self): + self._grade = 0 + + @property + def grade(self): + return self._grade + + @grade.setter + def grade(self, value): + if not (0 <= value <= 100): + raise ValueError( + 'Grade must be between 0 and 100') + self._grade = value + + +# Example 2 +galileo = Homework() +galileo.grade = 95 +assert galileo.grade == 95 + + +# Example 3 +class Exam: + def __init__(self): + self._writing_grade = 0 + self._math_grade = 0 + + @staticmethod + def _check_grade(value): + if not (0 <= value <= 100): + raise ValueError( + 'Grade must be between 0 and 100') + + +# Example 4 + @property + def writing_grade(self): + return self._writing_grade + + @writing_grade.setter + def writing_grade(self, value): + self._check_grade(value) + self._writing_grade = value + + @property + def math_grade(self): + return self._math_grade + + @math_grade.setter + def math_grade(self, value): + self._check_grade(value) + self._math_grade = value + +galileo = Exam() +galileo.writing_grade = 85 +galileo.math_grade = 99 + +assert galileo.writing_grade == 85 +assert galileo.math_grade == 99 + + +# Example 5 +class Grade: + def __get__(self, instance, instance_type): + pass + + def __set__(self, instance, value): + pass + +class Exam: + # Class attributes + math_grade = Grade() + writing_grade = Grade() + science_grade = Grade() + + +# Example 6 +exam = Exam() +exam.writing_grade = 40 + + +# Example 7 +Exam.__dict__['writing_grade'].__set__(exam, 40) + + +# Example 8 +exam.writing_grade + + +# Example 9 +Exam.__dict__['writing_grade'].__get__(exam, Exam) + + +# Example 10 +class Grade: + def __init__(self): + self._value = 0 + + def __get__(self, instance, instance_type): + return self._value + + def __set__(self, instance, value): + if not (0 <= value <= 100): + raise ValueError( + 'Grade must be between 0 and 100') + self._value = value + + +# Example 11 +class Exam: + math_grade = Grade() + writing_grade = Grade() + science_grade = Grade() + +first_exam = Exam() +first_exam.writing_grade = 82 +first_exam.science_grade = 99 +print('Writing', first_exam.writing_grade) +print('Science', first_exam.science_grade) + + +# Example 12 +second_exam = Exam() +second_exam.writing_grade = 75 +print(f'Second {second_exam.writing_grade} is right') +print(f'First {first_exam.writing_grade} is wrong; ' + f'should be 82') + + +# Example 13 +class Grade: + def __init__(self): + self._values = {} + + def __get__(self, instance, instance_type): + if instance is None: + return self + return self._values.get(instance, 0) + + def __set__(self, instance, value): + if not (0 <= value <= 100): + raise ValueError( + 'Grade must be between 0 and 100') + self._values[instance] = value + + +# Example 14 +from weakref import WeakKeyDictionary + +class Grade: + def __init__(self): + self._values = WeakKeyDictionary() + + def __get__(self, instance, instance_type): + if instance is None: + return self + return self._values.get(instance, 0) + + def __set__(self, instance, value): + if not (0 <= value <= 100): + raise ValueError( + 'Grade must be between 0 and 100') + self._values[instance] = value + + +# Example 15 +class Exam: + math_grade = Grade() + writing_grade = Grade() + science_grade = Grade() + +first_exam = Exam() +first_exam.writing_grade = 82 +second_exam = Exam() +second_exam.writing_grade = 75 +print(f'First {first_exam.writing_grade} is right') +print(f'Second {second_exam.writing_grade} is right') diff --git a/example_code/item_47.py b/example_code/item_47.py new file mode 100755 index 0000000..7f93c5f --- /dev/null +++ b/example_code/item_47.py @@ -0,0 +1,195 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class LazyRecord: + def __init__(self): + self.exists = 5 + + def __getattr__(self, name): + value = f'Value for {name}' + setattr(self, name, value) + return value + + +# Example 2 +data = LazyRecord() +print('Before:', data.__dict__) +print('foo: ', data.foo) +print('After: ', data.__dict__) + + +# Example 3 +class LoggingLazyRecord(LazyRecord): + def __getattr__(self, name): + print(f'* Called __getattr__({name!r}), ' + f'populating instance dictionary') + result = super().__getattr__(name) + print(f'* Returning {result!r}') + return result + +data = LoggingLazyRecord() +print('exists: ', data.exists) +print('First foo: ', data.foo) +print('Second foo: ', data.foo) + + +# Example 4 +class ValidatingRecord: + def __init__(self): + self.exists = 5 + + def __getattribute__(self, name): + print(f'* Called __getattribute__({name!r})') + try: + value = super().__getattribute__(name) + print(f'* Found {name!r}, returning {value!r}') + return value + except AttributeError: + value = f'Value for {name}' + print(f'* Setting {name!r} to {value!r}') + setattr(self, name, value) + return value + +data = ValidatingRecord() +print('exists: ', data.exists) +print('First foo: ', data.foo) +print('Second foo: ', data.foo) + + +# Example 5 +try: + class MissingPropertyRecord: + def __getattr__(self, name): + if name == 'bad_name': + raise AttributeError(f'{name} is missing') + value = f'Value for {name}' + setattr(self, name, value) + return value + + data = MissingPropertyRecord() + assert data.foo == 'Value for foo' # Test this works + data.bad_name +except: + logging.exception('Expected') +else: + assert False + + +# Example 6 +data = LoggingLazyRecord() # Implements __getattr__ +print('Before: ', data.__dict__) +print('Has first foo: ', hasattr(data, 'foo')) +print('After: ', data.__dict__) +print('Has second foo: ', hasattr(data, 'foo')) + + +# Example 7 +data = ValidatingRecord() # Implements __getattribute__ +print('Has first foo: ', hasattr(data, 'foo')) +print('Has second foo: ', hasattr(data, 'foo')) + + +# Example 8 +class SavingRecord: + def __setattr__(self, name, value): + # Save some data for the record + pass + super().__setattr__(name, value) + + +# Example 9 +class LoggingSavingRecord(SavingRecord): + def __setattr__(self, name, value): + print(f'* Called __setattr__({name!r}, {value!r})') + super().__setattr__(name, value) + +data = LoggingSavingRecord() +print('Before: ', data.__dict__) +data.foo = 5 +print('After: ', data.__dict__) +data.foo = 7 +print('Finally:', data.__dict__) + + +# Example 10 +class BrokenDictionaryRecord: + def __init__(self, data): + self._data = {} + + def __getattribute__(self, name): + print(f'* Called __getattribute__({name!r})') + return self._data[name] + + +# Example 11 +try: + data = BrokenDictionaryRecord({'foo': 3}) + data.foo +except: + logging.exception('Expected') +else: + assert False + + +# Example 12 +class DictionaryRecord: + def __init__(self, data): + self._data = data + + def __getattribute__(self, name): + # Prevent weird interactions with isinstance() used + # by example code harness. + if name == '__class__': + return DictionaryRecord + print(f'* Called __getattribute__({name!r})') + data_dict = super().__getattribute__('_data') + return data_dict[name] + +data = DictionaryRecord({'foo': 3}) +print('foo: ', data.foo) diff --git a/example_code/item_48.py b/example_code/item_48.py new file mode 100755 index 0000000..36000c8 --- /dev/null +++ b/example_code/item_48.py @@ -0,0 +1,303 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class Meta(type): + def __new__(meta, name, bases, class_dict): + global print + orig_print = print + print(f'* Running {meta}.__new__ for {name}') + print('Bases:', bases) + print = pprint + print(class_dict) + print = orig_print + return type.__new__(meta, name, bases, class_dict) + +class MyClass(metaclass=Meta): + stuff = 123 + + def foo(self): + pass + +class MySubclass(MyClass): + other = 567 + + def bar(self): + pass + + +# Example 2 +class ValidatePolygon(type): + def __new__(meta, name, bases, class_dict): + # Only validate subclasses of the Polygon class + if bases: + if class_dict['sides'] < 3: + raise ValueError('Polygons need 3+ sides') + return type.__new__(meta, name, bases, class_dict) + +class Polygon(metaclass=ValidatePolygon): + sides = None # Must be specified by subclasses + + @classmethod + def interior_angles(cls): + return (cls.sides - 2) * 180 + +class Triangle(Polygon): + sides = 3 + +class Rectangle(Polygon): + sides = 4 + +class Nonagon(Polygon): + sides = 9 + +assert Triangle.interior_angles() == 180 +assert Rectangle.interior_angles() == 360 +assert Nonagon.interior_angles() == 1260 + + +# Example 3 +try: + print('Before class') + + class Line(Polygon): + print('Before sides') + sides = 2 + print('After sides') + + print('After class') +except: + logging.exception('Expected') +else: + assert False + + +# Example 4 +class BetterPolygon: + sides = None # Must be specified by subclasses + + def __init_subclass__(cls): + super().__init_subclass__() + if cls.sides < 3: + raise ValueError('Polygons need 3+ sides') + + @classmethod + def interior_angles(cls): + return (cls.sides - 2) * 180 + +class Hexagon(BetterPolygon): + sides = 6 + +assert Hexagon.interior_angles() == 720 + + +# Example 5 +try: + print('Before class') + + class Point(BetterPolygon): + sides = 1 + + print('After class') +except: + logging.exception('Expected') +else: + assert False + + +# Example 6 +class ValidateFilled(type): + def __new__(meta, name, bases, class_dict): + # Only validate subclasses of the Filled class + if bases: + if class_dict['color'] not in ('red', 'green'): + raise ValueError('Fill color must be supported') + return type.__new__(meta, name, bases, class_dict) + +class Filled(metaclass=ValidateFilled): + color = None # Must be specified by subclasses + + +# Example 7 +try: + class RedPentagon(Filled, Polygon): + color = 'blue' + sides = 5 +except: + logging.exception('Expected') +else: + assert False + + +# Example 8 +class ValidatePolygon(type): + def __new__(meta, name, bases, class_dict): + # Only validate non-root classes + if not class_dict.get('is_root'): + if class_dict['sides'] < 3: + raise ValueError('Polygons need 3+ sides') + return type.__new__(meta, name, bases, class_dict) + +class Polygon(metaclass=ValidatePolygon): + is_root = True + sides = None # Must be specified by subclasses + +class ValidateFilledPolygon(ValidatePolygon): + def __new__(meta, name, bases, class_dict): + # Only validate non-root classes + if not class_dict.get('is_root'): + if class_dict['color'] not in ('red', 'green'): + raise ValueError('Fill color must be supported') + return super().__new__(meta, name, bases, class_dict) + +class FilledPolygon(Polygon, metaclass=ValidateFilledPolygon): + is_root = True + color = None # Must be specified by subclasses + + +# Example 9 +class GreenPentagon(FilledPolygon): + color = 'green' + sides = 5 + +greenie = GreenPentagon() +assert isinstance(greenie, Polygon) + + +# Example 10 +try: + class OrangePentagon(FilledPolygon): + color = 'orange' + sides = 5 +except: + logging.exception('Expected') +else: + assert False + + +# Example 11 +try: + class RedLine(FilledPolygon): + color = 'red' + sides = 2 +except: + logging.exception('Expected') +else: + assert False + + +# Example 12 +class Filled: + color = None # Must be specified by subclasses + + def __init_subclass__(cls): + super().__init_subclass__() + if cls.color not in ('red', 'green', 'blue'): + raise ValueError('Fills need a valid color') + + +# Example 13 +class RedTriangle(Filled, Polygon): + color = 'red' + sides = 3 + +ruddy = RedTriangle() +assert isinstance(ruddy, Filled) +assert isinstance(ruddy, Polygon) + + +# Example 14 +try: + print('Before class') + + class BlueLine(Filled, Polygon): + color = 'blue' + sides = 2 + + print('After class') +except: + logging.exception('Expected') +else: + assert False + + +# Example 15 +try: + print('Before class') + + class BeigeSquare(Filled, Polygon): + color = 'beige' + sides = 4 + + print('After class') +except: + logging.exception('Expected') +else: + assert False + + +# Example 16 +class Top: + def __init_subclass__(cls): + super().__init_subclass__() + print(f'Top for {cls}') + +class Left(Top): + def __init_subclass__(cls): + super().__init_subclass__() + print(f'Left for {cls}') + +class Right(Top): + def __init_subclass__(cls): + super().__init_subclass__() + print(f'Right for {cls}') + +class Bottom(Left, Right): + def __init_subclass__(cls): + super().__init_subclass__() + print(f'Bottom for {cls}') diff --git a/example_code/item_49.py b/example_code/item_49.py new file mode 100755 index 0000000..7dec89a --- /dev/null +++ b/example_code/item_49.py @@ -0,0 +1,212 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +import json + +class Serializable: + def __init__(self, *args): + self.args = args + + def serialize(self): + return json.dumps({'args': self.args}) + + +# Example 2 +class Point2D(Serializable): + def __init__(self, x, y): + super().__init__(x, y) + self.x = x + self.y = y + + def __repr__(self): + return f'Point2D({self.x}, {self.y})' + +point = Point2D(5, 3) +print('Object: ', point) +print('Serialized:', point.serialize()) + + +# Example 3 +class Deserializable(Serializable): + @classmethod + def deserialize(cls, json_data): + params = json.loads(json_data) + return cls(*params['args']) + + +# Example 4 +class BetterPoint2D(Deserializable): + def __init__(self, x, y): + super().__init__(x, y) + self.x = x + self.y = y + + def __repr__(self): + return f'Point2D({self.x}, {self.y})' + +before = BetterPoint2D(5, 3) +print('Before: ', before) +data = before.serialize() +print('Serialized:', data) +after = BetterPoint2D.deserialize(data) +print('After: ', after) + + +# Example 5 +class BetterSerializable: + def __init__(self, *args): + self.args = args + + def serialize(self): + return json.dumps({ + 'class': self.__class__.__name__, + 'args': self.args, + }) + + def __repr__(self): + name = self.__class__.__name__ + args_str = ', '.join(str(x) for x in self.args) + return f'{name}({args_str})' + + +# Example 6 +registry = {} + +def register_class(target_class): + registry[target_class.__name__] = target_class + +def deserialize(data): + params = json.loads(data) + name = params['class'] + target_class = registry[name] + return target_class(*params['args']) + + +# Example 7 +class EvenBetterPoint2D(BetterSerializable): + def __init__(self, x, y): + super().__init__(x, y) + self.x = x + self.y = y + +register_class(EvenBetterPoint2D) + + +# Example 8 +before = EvenBetterPoint2D(5, 3) +print('Before: ', before) +data = before.serialize() +print('Serialized:', data) +after = deserialize(data) +print('After: ', after) + + +# Example 9 +class Point3D(BetterSerializable): + def __init__(self, x, y, z): + super().__init__(x, y, z) + self.x = x + self.y = y + self.z = z + +# Forgot to call register_class! Whoops! + + +# Example 10 +try: + point = Point3D(5, 9, -4) + data = point.serialize() + deserialize(data) +except: + logging.exception('Expected') +else: + assert False + + +# Example 11 +class Meta(type): + def __new__(meta, name, bases, class_dict): + cls = type.__new__(meta, name, bases, class_dict) + register_class(cls) + return cls + +class RegisteredSerializable(BetterSerializable, + metaclass=Meta): + pass + + +# Example 12 +class Vector3D(RegisteredSerializable): + def __init__(self, x, y, z): + super().__init__(x, y, z) + self.x, self.y, self.z = x, y, z + +before = Vector3D(10, -7, 3) +print('Before: ', before) +data = before.serialize() +print('Serialized:', data) +print('After: ', deserialize(data)) + + +# Example 13 +class BetterRegisteredSerializable(BetterSerializable): + def __init_subclass__(cls): + super().__init_subclass__() + register_class(cls) + +class Vector1D(BetterRegisteredSerializable): + def __init__(self, magnitude): + super().__init__(magnitude) + self.magnitude = magnitude + +before = Vector1D(6) +print('Before: ', before) +data = before.serialize() +print('Serialized:', data) +print('After: ', deserialize(data)) diff --git a/example_code/item_50.py b/example_code/item_50.py new file mode 100755 index 0000000..2a26765 --- /dev/null +++ b/example_code/item_50.py @@ -0,0 +1,182 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class Field: + def __init__(self, name): + self.name = name + self.internal_name = '_' + self.name + + def __get__(self, instance, instance_type): + if instance is None: + return self + return getattr(instance, self.internal_name, '') + + def __set__(self, instance, value): + setattr(instance, self.internal_name, value) + + +# Example 2 +class Customer: + # Class attributes + first_name = Field('first_name') + last_name = Field('last_name') + prefix = Field('prefix') + suffix = Field('suffix') + + +# Example 3 +cust = Customer() +print(f'Before: {cust.first_name!r} {cust.__dict__}') +cust.first_name = 'Euclid' +print(f'After: {cust.first_name!r} {cust.__dict__}') + + +# Example 4 +class Customer: + # Left side is redundant with right side + first_name = Field('first_name') + last_name = Field('last_name') + prefix = Field('prefix') + suffix = Field('suffix') + + +# Example 5 +class Meta(type): + def __new__(meta, name, bases, class_dict): + for key, value in class_dict.items(): + if isinstance(value, Field): + value.name = key + value.internal_name = '_' + key + cls = type.__new__(meta, name, bases, class_dict) + return cls + + +# Example 6 +class DatabaseRow(metaclass=Meta): + pass + + +# Example 7 +class Field: + def __init__(self): + # These will be assigned by the metaclass. + self.name = None + self.internal_name = None + + def __get__(self, instance, instance_type): + if instance is None: + return self + return getattr(instance, self.internal_name, '') + + def __set__(self, instance, value): + setattr(instance, self.internal_name, value) + + +# Example 8 +class BetterCustomer(DatabaseRow): + first_name = Field() + last_name = Field() + prefix = Field() + suffix = Field() + + +# Example 9 +cust = BetterCustomer() +print(f'Before: {cust.first_name!r} {cust.__dict__}') +cust.first_name = 'Euler' +print(f'After: {cust.first_name!r} {cust.__dict__}') + + +# Example 10 +try: + class BrokenCustomer: + first_name = Field() + last_name = Field() + prefix = Field() + suffix = Field() + + cust = BrokenCustomer() + cust.first_name = 'Mersenne' +except: + logging.exception('Expected') +else: + assert False + + +# Example 11 +class Field: + def __init__(self): + self.name = None + self.internal_name = None + + def __set_name__(self, owner, name): + # Called on class creation for each descriptor + self.name = name + self.internal_name = '_' + name + + def __get__(self, instance, instance_type): + if instance is None: + return self + return getattr(instance, self.internal_name, '') + + def __set__(self, instance, value): + setattr(instance, self.internal_name, value) + + +# Example 12 +class FixedCustomer: + first_name = Field() + last_name = Field() + prefix = Field() + suffix = Field() + +cust = FixedCustomer() +print(f'Before: {cust.first_name!r} {cust.__dict__}') +cust.first_name = 'Mersenne' +print(f'After: {cust.first_name!r} {cust.__dict__}') diff --git a/example_code/item_51.py b/example_code/item_51.py new file mode 100755 index 0000000..0beacd7 --- /dev/null +++ b/example_code/item_51.py @@ -0,0 +1,243 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +from functools import wraps + +def trace_func(func): + if hasattr(func, 'tracing'): # Only decorate once + return func + + @wraps(func) + def wrapper(*args, **kwargs): + result = None + try: + result = func(*args, **kwargs) + return result + except Exception as e: + result = e + raise + finally: + print(f'{func.__name__}({args!r}, {kwargs!r}) -> ' + f'{result!r}') + + wrapper.tracing = True + return wrapper + + +# Example 2 +class TraceDict(dict): + @trace_func + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @trace_func + def __setitem__(self, *args, **kwargs): + return super().__setitem__(*args, **kwargs) + + @trace_func + def __getitem__(self, *args, **kwargs): + return super().__getitem__(*args, **kwargs) + + +# Example 3 +trace_dict = TraceDict([('hi', 1)]) +trace_dict['there'] = 2 +trace_dict['hi'] +try: + trace_dict['does not exist'] +except KeyError: + pass # Expected +else: + assert False + + +# Example 4 +import types + +trace_types = ( + types.MethodType, + types.FunctionType, + types.BuiltinFunctionType, + types.BuiltinMethodType, + types.MethodDescriptorType, + types.ClassMethodDescriptorType) + +class TraceMeta(type): + def __new__(meta, name, bases, class_dict): + klass = super().__new__(meta, name, bases, class_dict) + + for key in dir(klass): + value = getattr(klass, key) + if isinstance(value, trace_types): + wrapped = trace_func(value) + setattr(klass, key, wrapped) + + return klass + + +# Example 5 +class TraceDict(dict, metaclass=TraceMeta): + pass + +trace_dict = TraceDict([('hi', 1)]) +trace_dict['there'] = 2 +trace_dict['hi'] +try: + trace_dict['does not exist'] +except KeyError: + pass # Expected +else: + assert False + + +# Example 6 +try: + class OtherMeta(type): + pass + + class SimpleDict(dict, metaclass=OtherMeta): + pass + + class TraceDict(SimpleDict, metaclass=TraceMeta): + pass +except: + logging.exception('Expected') +else: + assert False + + +# Example 7 +class TraceMeta(type): + def __new__(meta, name, bases, class_dict): + klass = type.__new__(meta, name, bases, class_dict) + + for key in dir(klass): + value = getattr(klass, key) + if isinstance(value, trace_types): + wrapped = trace_func(value) + setattr(klass, key, wrapped) + + return klass + +class OtherMeta(TraceMeta): + pass + +class SimpleDict(dict, metaclass=OtherMeta): + pass + +class TraceDict(SimpleDict, metaclass=TraceMeta): + pass + +trace_dict = TraceDict([('hi', 1)]) +trace_dict['there'] = 2 +trace_dict['hi'] +try: + trace_dict['does not exist'] +except KeyError: + pass # Expected +else: + assert False + + +# Example 8 +def my_class_decorator(klass): + klass.extra_param = 'hello' + return klass + +@my_class_decorator +class MyClass: + pass + +print(MyClass) +print(MyClass.extra_param) + + +# Example 9 +def trace(klass): + for key in dir(klass): + value = getattr(klass, key) + if isinstance(value, trace_types): + wrapped = trace_func(value) + setattr(klass, key, wrapped) + return klass + + +# Example 10 +@trace +class TraceDict(dict): + pass + +trace_dict = TraceDict([('hi', 1)]) +trace_dict['there'] = 2 +trace_dict['hi'] +try: + trace_dict['does not exist'] +except KeyError: + pass # Expected +else: + assert False + + +# Example 11 +class OtherMeta(type): + pass + +@trace +class TraceDict(dict, metaclass=OtherMeta): + pass + +trace_dict = TraceDict([('hi', 1)]) +trace_dict['there'] = 2 +trace_dict['hi'] +try: + trace_dict['does not exist'] +except KeyError: + pass # Expected +else: + assert False diff --git a/example_code/item_52.py b/example_code/item_52.py new file mode 100755 index 0000000..d112055 --- /dev/null +++ b/example_code/item_52.py @@ -0,0 +1,182 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +import subprocess +# Enable these lines to make this example work on Windows +# import os +# os.environ['COMSPEC'] = 'powershell' + +result = subprocess.run( + ['echo', 'Hello from the child!'], + capture_output=True, + # Enable this line to make this example work on Windows + # shell=True, + encoding='utf-8') + +result.check_returncode() # No exception means it exited cleanly +print(result.stdout) + + +# Example 2 +# Use this line instead to make this example work on Windows +# proc = subprocess.Popen(['sleep', '1'], shell=True) +proc = subprocess.Popen(['sleep', '1']) +while proc.poll() is None: + print('Working...') + # Some time-consuming work here + import time + time.sleep(0.3) + +print('Exit status', proc.poll()) + + +# Example 3 +import time + +start = time.time() +sleep_procs = [] +for _ in range(10): + # Use this line instead to make this example work on Windows + # proc = subprocess.Popen(['sleep', '1'], shell=True) + proc = subprocess.Popen(['sleep', '1']) + sleep_procs.append(proc) + + +# Example 4 +for proc in sleep_procs: + proc.communicate() + +end = time.time() +delta = end - start +print(f'Finished in {delta:.3} seconds') + + +# Example 5 +import os +# On Windows, after installing OpenSSL, you may need to +# alias it in your PowerShell path with a command like: +# $env:path = $env:path + ";C:\Program Files\OpenSSL-Win64\bin" + +def run_encrypt(data): + env = os.environ.copy() + env['password'] = 'zf7ShyBhZOraQDdE/FiZpm/m/8f9X+M1' + proc = subprocess.Popen( + ['openssl', 'enc', '-des3', '-pass', 'env:password'], + env=env, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + proc.stdin.write(data) + proc.stdin.flush() # Ensure that the child gets input + return proc + + +# Example 6 +procs = [] +for _ in range(3): + data = os.urandom(10) + proc = run_encrypt(data) + procs.append(proc) + + +# Example 7 +for proc in procs: + out, _ = proc.communicate() + print(out[-10:]) + + +# Example 8 +def run_hash(input_stdin): + return subprocess.Popen( + ['openssl', 'dgst', '-whirlpool', '-binary'], + stdin=input_stdin, + stdout=subprocess.PIPE) + + +# Example 9 +encrypt_procs = [] +hash_procs = [] +for _ in range(3): + data = os.urandom(100) + + encrypt_proc = run_encrypt(data) + encrypt_procs.append(encrypt_proc) + + hash_proc = run_hash(encrypt_proc.stdout) + hash_procs.append(hash_proc) + + # Ensure that the child consumes the input stream and + # the communicate() method doesn't inadvertently steal + # input from the child. Also lets SIGPIPE propagate to + # the upstream process if the downstream process dies. + encrypt_proc.stdout.close() + encrypt_proc.stdout = None + + +# Example 10 +for proc in encrypt_procs: + proc.communicate() + assert proc.returncode == 0 + +for proc in hash_procs: + out, _ = proc.communicate() + print(out[-10:]) + assert proc.returncode == 0 + + +# Example 11 +# Use this line instead to make this example work on Windows +# proc = subprocess.Popen(['sleep', '10'], shell=True) +proc = subprocess.Popen(['sleep', '10']) +try: + proc.communicate(timeout=0.1) +except subprocess.TimeoutExpired: + proc.terminate() + proc.wait() + +print('Exit status', proc.poll()) diff --git a/example_code/item_53.py b/example_code/item_53.py new file mode 100755 index 0000000..fdab48f --- /dev/null +++ b/example_code/item_53.py @@ -0,0 +1,142 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +def factorize(number): + for i in range(1, number + 1): + if number % i == 0: + yield i + + +# Example 2 +import time + +numbers = [2139079, 1214759, 1516637, 1852285] +start = time.time() + +for number in numbers: + list(factorize(number)) + +end = time.time() +delta = end - start +print(f'Took {delta:.3f} seconds') + + +# Example 3 +from threading import Thread + +class FactorizeThread(Thread): + def __init__(self, number): + super().__init__() + self.number = number + + def run(self): + self.factors = list(factorize(self.number)) + + +# Example 4 +start = time.time() + +threads = [] +for number in numbers: + thread = FactorizeThread(number) + thread.start() + threads.append(thread) + + +# Example 5 +for thread in threads: + thread.join() + +end = time.time() +delta = end - start +print(f'Took {delta:.3f} seconds') + + +# Example 6 +import select +import socket + +def slow_systemcall(): + select.select([socket.socket()], [], [], 0.1) + + +# Example 7 +start = time.time() + +for _ in range(5): + slow_systemcall() + +end = time.time() +delta = end - start +print(f'Took {delta:.3f} seconds') + + +# Example 8 +start = time.time() + +threads = [] +for _ in range(5): + thread = Thread(target=slow_systemcall) + thread.start() + threads.append(thread) + + +# Example 9 +def compute_helicopter_location(index): + pass + +for i in range(5): + compute_helicopter_location(i) + +for thread in threads: + thread.join() + +end = time.time() +delta = end - start +print(f'Took {delta:.3f} seconds') diff --git a/example_code/item_54.py b/example_code/item_54.py new file mode 100755 index 0000000..60636f4 --- /dev/null +++ b/example_code/item_54.py @@ -0,0 +1,144 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class Counter: + def __init__(self): + self.count = 0 + + def increment(self, offset): + self.count += offset + + +# Example 2 +def worker(sensor_index, how_many, counter): + # I have a barrier in here so the workers synchronize + # when they start counting, otherwise it's hard to get a race + # because the overhead of starting a thread is high. + BARRIER.wait() + for _ in range(how_many): + # Read from the sensor + # Nothing actually happens here, but this is where + # the blocking I/O would go. + counter.increment(1) + + +# Example 3 +from threading import Barrier +BARRIER = Barrier(5) +from threading import Thread + +how_many = 10**5 +counter = Counter() + +threads = [] +for i in range(5): + thread = Thread(target=worker, + args=(i, how_many, counter)) + threads.append(thread) + thread.start() + +for thread in threads: + thread.join() + +expected = how_many * 5 +found = counter.count +print(f'Counter should be {expected}, got {found}') + + +# Example 4 +counter.count += 1 + + +# Example 5 +value = getattr(counter, 'count') +result = value + 1 +setattr(counter, 'count', result) + + +# Example 6 +# Running in Thread A +value_a = getattr(counter, 'count') +# Context switch to Thread B +value_b = getattr(counter, 'count') +result_b = value_b + 1 +setattr(counter, 'count', result_b) +# Context switch back to Thread A +result_a = value_a + 1 +setattr(counter, 'count', result_a) + + +# Example 7 +from threading import Lock + +class LockingCounter: + def __init__(self): + self.lock = Lock() + self.count = 0 + + def increment(self, offset): + with self.lock: + self.count += offset + + +# Example 8 +BARRIER = Barrier(5) +counter = LockingCounter() + +for i in range(5): + thread = Thread(target=worker, + args=(i, how_many, counter)) + threads.append(thread) + thread.start() + +for thread in threads: + thread.join() + +expected = how_many * 5 +found = counter.count +print(f'Counter should be {expected}, got {found}') diff --git a/example_code/item_55.py b/example_code/item_55.py new file mode 100755 index 0000000..86696b2 --- /dev/null +++ b/example_code/item_55.py @@ -0,0 +1,325 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +def download(item): + return item + +def resize(item): + return item + +def upload(item): + return item + + +# Example 2 +from collections import deque +from threading import Lock + +class MyQueue: + def __init__(self): + self.items = deque() + self.lock = Lock() + + +# Example 3 + def put(self, item): + with self.lock: + self.items.append(item) + + +# Example 4 + def get(self): + with self.lock: + return self.items.popleft() + + +# Example 5 +from threading import Thread +import time + +class Worker(Thread): + def __init__(self, func, in_queue, out_queue): + super().__init__() + self.func = func + self.in_queue = in_queue + self.out_queue = out_queue + self.polled_count = 0 + self.work_done = 0 + + +# Example 6 + def run(self): + while True: + self.polled_count += 1 + try: + item = self.in_queue.get() + except IndexError: + time.sleep(0.01) # No work to do + except AttributeError: + # The magic exit signal + return + else: + result = self.func(item) + self.out_queue.put(result) + self.work_done += 1 + + +# Example 7 +download_queue = MyQueue() +resize_queue = MyQueue() +upload_queue = MyQueue() +done_queue = MyQueue() +threads = [ + Worker(download, download_queue, resize_queue), + Worker(resize, resize_queue, upload_queue), + Worker(upload, upload_queue, done_queue), +] + + +# Example 8 +for thread in threads: + thread.start() + +for _ in range(1000): + download_queue.put(object()) + + +# Example 9 +while len(done_queue.items) < 1000: + # Do something useful while waiting + time.sleep(0.1) +# Stop all the threads by causing an exception in their +# run methods. +for thread in threads: + thread.in_queue = None + thread.join() + + +# Example 10 +processed = len(done_queue.items) +polled = sum(t.polled_count for t in threads) +print(f'Processed {processed} items after ' + f'polling {polled} times') + + +# Example 11 +from queue import Queue + +my_queue = Queue() + +def consumer(): + print('Consumer waiting') + my_queue.get() # Runs after put() below + print('Consumer done') + +thread = Thread(target=consumer) +thread.start() + + +# Example 12 +print('Producer putting') +my_queue.put(object()) # Runs before get() above +print('Producer done') +thread.join() + + +# Example 13 +my_queue = Queue(1) # Buffer size of 1 + +def consumer(): + time.sleep(0.1) # Wait + my_queue.get() # Runs second + print('Consumer got 1') + my_queue.get() # Runs fourth + print('Consumer got 2') + print('Consumer done') + +thread = Thread(target=consumer) +thread.start() + + +# Example 14 +my_queue.put(object()) # Runs first +print('Producer put 1') +my_queue.put(object()) # Runs third +print('Producer put 2') +print('Producer done') +thread.join() + + +# Example 15 +in_queue = Queue() + +def consumer(): + print('Consumer waiting') + work = in_queue.get() # Done second + print('Consumer working') + # Doing work + print('Consumer done') + in_queue.task_done() # Done third + +thread = Thread(target=consumer) +thread.start() + + +# Example 16 +print('Producer putting') +in_queue.put(object()) # Done first +print('Producer waiting') +in_queue.join() # Done fourth +print('Producer done') +thread.join() + + +# Example 17 +class ClosableQueue(Queue): + SENTINEL = object() + + def close(self): + self.put(self.SENTINEL) + + +# Example 18 + def __iter__(self): + while True: + item = self.get() + try: + if item is self.SENTINEL: + return # Cause the thread to exit + yield item + finally: + self.task_done() + + +# Example 19 +class StoppableWorker(Thread): + def __init__(self, func, in_queue, out_queue): + super().__init__() + self.func = func + self.in_queue = in_queue + self.out_queue = out_queue + + def run(self): + for item in self.in_queue: + result = self.func(item) + self.out_queue.put(result) + + +# Example 20 +download_queue = ClosableQueue() +resize_queue = ClosableQueue() +upload_queue = ClosableQueue() +done_queue = ClosableQueue() +threads = [ + StoppableWorker(download, download_queue, resize_queue), + StoppableWorker(resize, resize_queue, upload_queue), + StoppableWorker(upload, upload_queue, done_queue), +] + + +# Example 21 +for thread in threads: + thread.start() + +for _ in range(1000): + download_queue.put(object()) + +download_queue.close() + + +# Example 22 +download_queue.join() +resize_queue.close() +resize_queue.join() +upload_queue.close() +upload_queue.join() +print(done_queue.qsize(), 'items finished') + +for thread in threads: + thread.join() + + +# Example 23 +def start_threads(count, *args): + threads = [StoppableWorker(*args) for _ in range(count)] + for thread in threads: + thread.start() + return threads + +def stop_threads(closable_queue, threads): + for _ in threads: + closable_queue.close() + + closable_queue.join() + + for thread in threads: + thread.join() + + +# Example 24 +download_queue = ClosableQueue() +resize_queue = ClosableQueue() +upload_queue = ClosableQueue() +done_queue = ClosableQueue() + +download_threads = start_threads( + 3, download, download_queue, resize_queue) +resize_threads = start_threads( + 4, resize, resize_queue, upload_queue) +upload_threads = start_threads( + 5, upload, upload_queue, done_queue) + +for _ in range(1000): + download_queue.put(object()) + +stop_threads(download_queue, download_threads) +stop_threads(resize_queue, resize_threads) +stop_threads(upload_queue, upload_threads) + +print(done_queue.qsize(), 'items finished') diff --git a/example_code/item_56.py b/example_code/item_56.py new file mode 100755 index 0000000..b01e83b --- /dev/null +++ b/example_code/item_56.py @@ -0,0 +1,228 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +ALIVE = '*' +EMPTY = '-' + + +# Example 2 +class Grid: + def __init__(self, height, width): + self.height = height + self.width = width + self.rows = [] + for _ in range(self.height): + self.rows.append([EMPTY] * self.width) + + def get(self, y, x): + return self.rows[y % self.height][x % self.width] + + def set(self, y, x, state): + self.rows[y % self.height][x % self.width] = state + + def __str__(self): + output = '' + for row in self.rows: + for cell in row: + output += cell + output += '\n' + return output + + +# Example 3 +grid = Grid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) +print(grid) + + +# Example 4 +def count_neighbors(y, x, get): + n_ = get(y - 1, x + 0) # North + ne = get(y - 1, x + 1) # Northeast + e_ = get(y + 0, x + 1) # East + se = get(y + 1, x + 1) # Southeast + s_ = get(y + 1, x + 0) # South + sw = get(y + 1, x - 1) # Southwest + w_ = get(y + 0, x - 1) # West + nw = get(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +alive = {(9, 5), (9, 6)} +seen = set() + +def fake_get(y, x): + position = (y, x) + seen.add(position) + return ALIVE if position in alive else EMPTY + +count = count_neighbors(10, 5, fake_get) +assert count == 2 + +expected_seen = { + (9, 5), (9, 6), (10, 6), (11, 6), + (11, 5), (11, 4), (10, 4), (9, 4) +} +assert seen == expected_seen + + +# Example 5 +def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY # Die: Too few + elif neighbors > 3: + return EMPTY # Die: Too many + else: + if neighbors == 3: + return ALIVE # Regenerate + return state + +assert game_logic(ALIVE, 0) == EMPTY +assert game_logic(ALIVE, 1) == EMPTY +assert game_logic(ALIVE, 2) == ALIVE +assert game_logic(ALIVE, 3) == ALIVE +assert game_logic(ALIVE, 4) == EMPTY +assert game_logic(EMPTY, 0) == EMPTY +assert game_logic(EMPTY, 1) == EMPTY +assert game_logic(EMPTY, 2) == EMPTY +assert game_logic(EMPTY, 3) == ALIVE +assert game_logic(EMPTY, 4) == EMPTY + + +# Example 6 +def step_cell(y, x, get, set): + state = get(y, x) + neighbors = count_neighbors(y, x, get) + next_state = game_logic(state, neighbors) + set(y, x, next_state) + +alive = {(10, 5), (9, 5), (9, 6)} +new_state = None + +def fake_get(y, x): + return ALIVE if (y, x) in alive else EMPTY + +def fake_set(y, x, state): + global new_state + new_state = state + +# Stay alive +step_cell(10, 5, fake_get, fake_set) +assert new_state == ALIVE + +# Stay dead +alive.remove((10, 5)) +step_cell(10, 5, fake_get, fake_set) +assert new_state == EMPTY + +# Regenerate +alive.add((10, 6)) +step_cell(10, 5, fake_get, fake_set) +assert new_state == ALIVE + + +# Example 7 +def simulate(grid): + next_grid = Grid(grid.height, grid.width) + for y in range(grid.height): + for x in range(grid.width): + step_cell(y, x, grid.get, next_grid.set) + return next_grid + + +# Example 8 +class ColumnPrinter: + def __init__(self): + self.columns = [] + + def append(self, data): + self.columns.append(data) + + def __str__(self): + row_count = 1 + for data in self.columns: + row_count = max( + row_count, len(data.splitlines()) + 1) + + rows = [''] * row_count + for j in range(row_count): + for i, data in enumerate(self.columns): + line = data.splitlines()[max(0, j - 1)] + if j == 0: + padding = ' ' * (len(line) // 2) + rows[j] += padding + str(i) + padding + else: + rows[j] += line + + if (i + 1) < len(self.columns): + rows[j] += ' | ' + + return '\n'.join(rows) + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = simulate(grid) + +print(columns) + + +# Example 9 +def game_logic(state, neighbors): + # Do some blocking input/output in here: + data = my_socket.recv(100) diff --git a/example_code/item_57.py b/example_code/item_57.py new file mode 100755 index 0000000..b236a72 --- /dev/null +++ b/example_code/item_57.py @@ -0,0 +1,211 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +from threading import Lock + +ALIVE = '*' +EMPTY = '-' + +class Grid: + def __init__(self, height, width): + self.height = height + self.width = width + self.rows = [] + for _ in range(self.height): + self.rows.append([EMPTY] * self.width) + + def get(self, y, x): + return self.rows[y % self.height][x % self.width] + + def set(self, y, x, state): + self.rows[y % self.height][x % self.width] = state + + def __str__(self): + output = '' + for row in self.rows: + for cell in row: + output += cell + output += '\n' + return output + +class LockingGrid(Grid): + def __init__(self, height, width): + super().__init__(height, width) + self.lock = Lock() + + def __str__(self): + with self.lock: + return super().__str__() + + def get(self, y, x): + with self.lock: + return super().get(y, x) + + def set(self, y, x, state): + with self.lock: + return super().set(y, x, state) + + +# Example 2 +from threading import Thread + +def count_neighbors(y, x, get): + n_ = get(y - 1, x + 0) # North + ne = get(y - 1, x + 1) # Northeast + e_ = get(y + 0, x + 1) # East + se = get(y + 1, x + 1) # Southeast + s_ = get(y + 1, x + 0) # South + sw = get(y + 1, x - 1) # Southwest + w_ = get(y + 0, x - 1) # West + nw = get(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +def game_logic(state, neighbors): + # Do some blocking input/output in here: + data = my_socket.recv(100) + +def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY # Die: Too few + elif neighbors > 3: + return EMPTY # Die: Too many + else: + if neighbors == 3: + return ALIVE # Regenerate + return state + +def step_cell(y, x, get, set): + state = get(y, x) + neighbors = count_neighbors(y, x, get) + next_state = game_logic(state, neighbors) + set(y, x, next_state) + +def simulate_threaded(grid): + next_grid = LockingGrid(grid.height, grid.width) + + threads = [] + for y in range(grid.height): + for x in range(grid.width): + args = (y, x, grid.get, next_grid.set) + thread = Thread(target=step_cell, args=args) + thread.start() # Fan out + threads.append(thread) + + for thread in threads: + thread.join() # Fan in + + return next_grid + + +# Example 3 +class ColumnPrinter: + def __init__(self): + self.columns = [] + + def append(self, data): + self.columns.append(data) + + def __str__(self): + row_count = 1 + for data in self.columns: + row_count = max( + row_count, len(data.splitlines()) + 1) + + rows = [''] * row_count + for j in range(row_count): + for i, data in enumerate(self.columns): + line = data.splitlines()[max(0, j - 1)] + if j == 0: + padding = ' ' * (len(line) // 2) + rows[j] += padding + str(i) + padding + else: + rows[j] += line + + if (i + 1) < len(self.columns): + rows[j] += ' | ' + + return '\n'.join(rows) + +grid = LockingGrid(5, 9) # Changed +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = simulate_threaded(grid) # Changed + +print(columns) + + +# Example 4 +def game_logic(state, neighbors): + raise OSError('Problem with I/O') + + +# Example 5 +import contextlib +import io + +fake_stderr = io.StringIO() +with contextlib.redirect_stderr(fake_stderr): + thread = Thread(target=game_logic, args=(ALIVE, 3)) + thread.start() + thread.join() + +print(fake_stderr.getvalue()) diff --git a/example_code/item_58.py b/example_code/item_58.py new file mode 100755 index 0000000..023d441 --- /dev/null +++ b/example_code/item_58.py @@ -0,0 +1,413 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +from queue import Queue + +class ClosableQueue(Queue): + SENTINEL = object() + + def close(self): + self.put(self.SENTINEL) + + def __iter__(self): + while True: + item = self.get() + try: + if item is self.SENTINEL: + return # Cause the thread to exit + yield item + finally: + self.task_done() + +in_queue = ClosableQueue() +out_queue = ClosableQueue() + + +# Example 2 +from threading import Thread + +class StoppableWorker(Thread): + def __init__(self, func, in_queue, out_queue, **kwargs): + super().__init__(**kwargs) + self.func = func + self.in_queue = in_queue + self.out_queue = out_queue + + def run(self): + for item in self.in_queue: + result = self.func(item) + self.out_queue.put(result) + +def game_logic(state, neighbors): + # Do some blocking input/output in here: + data = my_socket.recv(100) + +def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY # Die: Too few + elif neighbors > 3: + return EMPTY # Die: Too many + else: + if neighbors == 3: + return ALIVE # Regenerate + return state + +def game_logic_thread(item): + y, x, state, neighbors = item + try: + next_state = game_logic(state, neighbors) + except Exception as e: + next_state = e + return (y, x, next_state) + +# Start the threads upfront +threads = [] +for _ in range(5): + thread = StoppableWorker( + game_logic_thread, in_queue, out_queue) + thread.start() + threads.append(thread) + + +# Example 3 +ALIVE = '*' +EMPTY = '-' + +class SimulationError(Exception): + pass + +class Grid: + def __init__(self, height, width): + self.height = height + self.width = width + self.rows = [] + for _ in range(self.height): + self.rows.append([EMPTY] * self.width) + + def get(self, y, x): + return self.rows[y % self.height][x % self.width] + + def set(self, y, x, state): + self.rows[y % self.height][x % self.width] = state + + def __str__(self): + output = '' + for row in self.rows: + for cell in row: + output += cell + output += '\n' + return output + +def count_neighbors(y, x, get): + n_ = get(y - 1, x + 0) # North + ne = get(y - 1, x + 1) # Northeast + e_ = get(y + 0, x + 1) # East + se = get(y + 1, x + 1) # Southeast + s_ = get(y + 1, x + 0) # South + sw = get(y + 1, x - 1) # Southwest + w_ = get(y + 0, x - 1) # West + nw = get(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +def simulate_pipeline(grid, in_queue, out_queue): + for y in range(grid.height): + for x in range(grid.width): + state = grid.get(y, x) + neighbors = count_neighbors(y, x, grid.get) + in_queue.put((y, x, state, neighbors)) # Fan out + + in_queue.join() + out_queue.close() + + next_grid = Grid(grid.height, grid.width) + for item in out_queue: # Fan in + y, x, next_state = item + if isinstance(next_state, Exception): + raise SimulationError(y, x) from next_state + next_grid.set(y, x, next_state) + + return next_grid + + +# Example 4 +try: + def game_logic(state, neighbors): + raise OSError('Problem with I/O in game_logic') + + simulate_pipeline(Grid(1, 1), in_queue, out_queue) +except: + logging.exception('Expected') +else: + assert False + + +# Example 5 +# Restore the working version of this function +def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY # Die: Too few + elif neighbors > 3: + return EMPTY # Die: Too many + else: + if neighbors == 3: + return ALIVE # Regenerate + return state + +class ColumnPrinter: + def __init__(self): + self.columns = [] + + def append(self, data): + self.columns.append(data) + + def __str__(self): + row_count = 1 + for data in self.columns: + row_count = max( + row_count, len(data.splitlines()) + 1) + + rows = [''] * row_count + for j in range(row_count): + for i, data in enumerate(self.columns): + line = data.splitlines()[max(0, j - 1)] + if j == 0: + padding = ' ' * (len(line) // 2) + rows[j] += padding + str(i) + padding + else: + rows[j] += line + + if (i + 1) < len(self.columns): + rows[j] += ' | ' + + return '\n'.join(rows) + +grid = Grid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = simulate_pipeline(grid, in_queue, out_queue) + +print(columns) + +for thread in threads: + in_queue.close() +for thread in threads: + thread.join() + + +# Example 6 +def count_neighbors(y, x, get): + # Do some blocking input/output in here: + data = my_socket.recv(100) + + +# Example 7 +def count_neighbors(y, x, get): + n_ = get(y - 1, x + 0) # North + ne = get(y - 1, x + 1) # Northeast + e_ = get(y + 0, x + 1) # East + se = get(y + 1, x + 1) # Southeast + s_ = get(y + 1, x + 0) # South + sw = get(y + 1, x - 1) # Southwest + w_ = get(y + 0, x - 1) # West + nw = get(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +def count_neighbors_thread(item): + y, x, state, get = item + try: + neighbors = count_neighbors(y, x, get) + except Exception as e: + neighbors = e + return (y, x, state, neighbors) + +def game_logic_thread(item): + y, x, state, neighbors = item + if isinstance(neighbors, Exception): + next_state = neighbors + else: + try: + next_state = game_logic(state, neighbors) + except Exception as e: + next_state = e + return (y, x, next_state) + +from threading import Lock + +class LockingGrid(Grid): + def __init__(self, height, width): + super().__init__(height, width) + self.lock = Lock() + + def __str__(self): + with self.lock: + return super().__str__() + + def get(self, y, x): + with self.lock: + return super().get(y, x) + + def set(self, y, x, state): + with self.lock: + return super().set(y, x, state) + + +# Example 8 +in_queue = ClosableQueue() +logic_queue = ClosableQueue() +out_queue = ClosableQueue() + +threads = [] + +for _ in range(5): + thread = StoppableWorker( + count_neighbors_thread, in_queue, logic_queue) + thread.start() + threads.append(thread) + +for _ in range(5): + thread = StoppableWorker( + game_logic_thread, logic_queue, out_queue) + thread.start() + threads.append(thread) + + +# Example 9 +def simulate_phased_pipeline( + grid, in_queue, logic_queue, out_queue): + for y in range(grid.height): + for x in range(grid.width): + state = grid.get(y, x) + item = (y, x, state, grid.get) + in_queue.put(item) # Fan out + + in_queue.join() + logic_queue.join() # Pipeline sequencing + out_queue.close() + + next_grid = LockingGrid(grid.height, grid.width) + for item in out_queue: # Fan in + y, x, next_state = item + if isinstance(next_state, Exception): + raise SimulationError(y, x) from next_state + next_grid.set(y, x, next_state) + + return next_grid + + +# Example 10 +grid = LockingGrid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = simulate_phased_pipeline( + grid, in_queue, logic_queue, out_queue) + +print(columns) + +for thread in threads: + in_queue.close() +for thread in threads: + logic_queue.close() +for thread in threads: + thread.join() + + +# Example 11 +# Make sure exception propagation works as expected +def count_neighbors(*args): + raise OSError('Problem with I/O in count_neighbors') + +in_queue = ClosableQueue() +logic_queue = ClosableQueue() +out_queue = ClosableQueue() + +threads = [ + StoppableWorker( + count_neighbors_thread, in_queue, logic_queue, + daemon=True), + StoppableWorker( + game_logic_thread, logic_queue, out_queue, + daemon=True), +] + +for thread in threads: + thread.start() + +try: + simulate_phased_pipeline( + grid, in_queue, logic_queue, out_queue) +except SimulationError: + pass # Expected +else: + assert False diff --git a/example_code/item_59.py b/example_code/item_59.py new file mode 100755 index 0000000..bd4ca10 --- /dev/null +++ b/example_code/item_59.py @@ -0,0 +1,207 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +ALIVE = '*' +EMPTY = '-' + +class Grid: + def __init__(self, height, width): + self.height = height + self.width = width + self.rows = [] + for _ in range(self.height): + self.rows.append([EMPTY] * self.width) + + def get(self, y, x): + return self.rows[y % self.height][x % self.width] + + def set(self, y, x, state): + self.rows[y % self.height][x % self.width] = state + + def __str__(self): + output = '' + for row in self.rows: + for cell in row: + output += cell + output += '\n' + return output + +from threading import Lock + +class LockingGrid(Grid): + def __init__(self, height, width): + super().__init__(height, width) + self.lock = Lock() + + def __str__(self): + with self.lock: + return super().__str__() + + def get(self, y, x): + with self.lock: + return super().get(y, x) + + def set(self, y, x, state): + with self.lock: + return super().set(y, x, state) + +def count_neighbors(y, x, get): + n_ = get(y - 1, x + 0) # North + ne = get(y - 1, x + 1) # Northeast + e_ = get(y + 0, x + 1) # East + se = get(y + 1, x + 1) # Southeast + s_ = get(y + 1, x + 0) # South + sw = get(y + 1, x - 1) # Southwest + w_ = get(y + 0, x - 1) # West + nw = get(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +def game_logic(state, neighbors): + # Do some blocking input/output in here: + data = my_socket.recv(100) + +def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY # Die: Too few + elif neighbors > 3: + return EMPTY # Die: Too many + else: + if neighbors == 3: + return ALIVE # Regenerate + return state + +def step_cell(y, x, get, set): + state = get(y, x) + neighbors = count_neighbors(y, x, get) + next_state = game_logic(state, neighbors) + set(y, x, next_state) + + +# Example 2 +from concurrent.futures import ThreadPoolExecutor + +def simulate_pool(pool, grid): + next_grid = LockingGrid(grid.height, grid.width) + + futures = [] + for y in range(grid.height): + for x in range(grid.width): + args = (y, x, grid.get, next_grid.set) + future = pool.submit(step_cell, *args) # Fan out + futures.append(future) + + for future in futures: + future.result() # Fan in + + return next_grid + + +# Example 3 +class ColumnPrinter: + def __init__(self): + self.columns = [] + + def append(self, data): + self.columns.append(data) + + def __str__(self): + row_count = 1 + for data in self.columns: + row_count = max( + row_count, len(data.splitlines()) + 1) + + rows = [''] * row_count + for j in range(row_count): + for i, data in enumerate(self.columns): + line = data.splitlines()[max(0, j - 1)] + if j == 0: + padding = ' ' * (len(line) // 2) + rows[j] += padding + str(i) + padding + else: + rows[j] += line + + if (i + 1) < len(self.columns): + rows[j] += ' | ' + + return '\n'.join(rows) + +grid = LockingGrid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +with ThreadPoolExecutor(max_workers=10) as pool: + for i in range(5): + columns.append(str(grid)) + grid = simulate_pool(pool, grid) + +print(columns) + + +# Example 4 +try: + def game_logic(state, neighbors): + raise OSError('Problem with I/O') + + with ThreadPoolExecutor(max_workers=10) as pool: + task = pool.submit(game_logic, ALIVE, 3) + task.result() +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_60.py b/example_code/item_60.py new file mode 100755 index 0000000..5e1c0b4 --- /dev/null +++ b/example_code/item_60.py @@ -0,0 +1,247 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +ALIVE = '*' +EMPTY = '-' + +class Grid: + def __init__(self, height, width): + self.height = height + self.width = width + self.rows = [] + for _ in range(self.height): + self.rows.append([EMPTY] * self.width) + + def get(self, y, x): + return self.rows[y % self.height][x % self.width] + + def set(self, y, x, state): + self.rows[y % self.height][x % self.width] = state + + def __str__(self): + output = '' + for row in self.rows: + for cell in row: + output += cell + output += '\n' + return output + +def count_neighbors(y, x, get): + n_ = get(y - 1, x + 0) # North + ne = get(y - 1, x + 1) # Northeast + e_ = get(y + 0, x + 1) # East + se = get(y + 1, x + 1) # Southeast + s_ = get(y + 1, x + 0) # South + sw = get(y + 1, x - 1) # Southwest + w_ = get(y + 0, x - 1) # West + nw = get(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +async def game_logic(state, neighbors): + # Do some input/output in here: + data = await my_socket.read(50) + +async def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY # Die: Too few + elif neighbors > 3: + return EMPTY # Die: Too many + else: + if neighbors == 3: + return ALIVE # Regenerate + return state + + +# Example 2 +async def step_cell(y, x, get, set): + state = get(y, x) + neighbors = count_neighbors(y, x, get) + next_state = await game_logic(state, neighbors) + set(y, x, next_state) + + +# Example 3 +import asyncio + +async def simulate(grid): + next_grid = Grid(grid.height, grid.width) + + tasks = [] + for y in range(grid.height): + for x in range(grid.width): + task = step_cell( + y, x, grid.get, next_grid.set) # Fan out + tasks.append(task) + + await asyncio.gather(*tasks) # Fan in + + return next_grid + + +# Example 4 +class ColumnPrinter: + def __init__(self): + self.columns = [] + + def append(self, data): + self.columns.append(data) + + def __str__(self): + row_count = 1 + for data in self.columns: + row_count = max( + row_count, len(data.splitlines()) + 1) + + rows = [''] * row_count + for j in range(row_count): + for i, data in enumerate(self.columns): + line = data.splitlines()[max(0, j - 1)] + if j == 0: + padding = ' ' * (len(line) // 2) + rows[j] += padding + str(i) + padding + else: + rows[j] += line + + if (i + 1) < len(self.columns): + rows[j] += ' | ' + + return '\n'.join(rows) + +logging.getLogger().setLevel(logging.ERROR) + +grid = Grid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = asyncio.run(simulate(grid)) # Run the event loop + +print(columns) + +logging.getLogger().setLevel(logging.DEBUG) + + +# Example 5 +try: + async def game_logic(state, neighbors): + raise OSError('Problem with I/O') + + logging.getLogger().setLevel(logging.ERROR) + + asyncio.run(game_logic(ALIVE, 3)) + + logging.getLogger().setLevel(logging.DEBUG) +except: + logging.exception('Expected') +else: + assert False + + +# Example 6 +async def count_neighbors(y, x, get): + n_ = get(y - 1, x + 0) # North + ne = get(y - 1, x + 1) # Northeast + e_ = get(y + 0, x + 1) # East + se = get(y + 1, x + 1) # Southeast + s_ = get(y + 1, x + 0) # South + sw = get(y + 1, x - 1) # Southwest + w_ = get(y + 0, x - 1) # West + nw = get(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +async def step_cell(y, x, get, set): + state = get(y, x) + neighbors = await count_neighbors(y, x, get) + next_state = await game_logic(state, neighbors) + set(y, x, next_state) + +async def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY # Die: Too few + elif neighbors > 3: + return EMPTY # Die: Too many + else: + if neighbors == 3: + return ALIVE # Regenerate + return state + +logging.getLogger().setLevel(logging.ERROR) + +grid = Grid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = asyncio.run(simulate(grid)) + +print(columns) + +logging.getLogger().setLevel(logging.DEBUG) diff --git a/example_code/item_61.py b/example_code/item_61.py new file mode 100755 index 0000000..96ed4bb --- /dev/null +++ b/example_code/item_61.py @@ -0,0 +1,454 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class EOFError(Exception): + pass + +class ConnectionBase: + def __init__(self, connection): + self.connection = connection + self.file = connection.makefile('rb') + + def send(self, command): + line = command + '\n' + data = line.encode() + self.connection.send(data) + + def receive(self): + line = self.file.readline() + if not line: + raise EOFError('Connection closed') + return line[:-1].decode() + + +# Example 2 +import random + +WARMER = 'Warmer' +COLDER = 'Colder' +UNSURE = 'Unsure' +CORRECT = 'Correct' + +class UnknownCommandError(Exception): + pass + +class Session(ConnectionBase): + def __init__(self, *args): + super().__init__(*args) + self._clear_state(None, None) + + def _clear_state(self, lower, upper): + self.lower = lower + self.upper = upper + self.secret = None + self.guesses = [] + + +# Example 3 + def loop(self): + while command := self.receive(): + parts = command.split(' ') + if parts[0] == 'PARAMS': + self.set_params(parts) + elif parts[0] == 'NUMBER': + self.send_number() + elif parts[0] == 'REPORT': + self.receive_report(parts) + else: + raise UnknownCommandError(command) + + +# Example 4 + def set_params(self, parts): + assert len(parts) == 3 + lower = int(parts[1]) + upper = int(parts[2]) + self._clear_state(lower, upper) + + +# Example 5 + def next_guess(self): + if self.secret is not None: + return self.secret + + while True: + guess = random.randint(self.lower, self.upper) + if guess not in self.guesses: + return guess + + def send_number(self): + guess = self.next_guess() + self.guesses.append(guess) + self.send(format(guess)) + + +# Example 6 + def receive_report(self, parts): + assert len(parts) == 2 + decision = parts[1] + + last = self.guesses[-1] + if decision == CORRECT: + self.secret = last + + print(f'Server: {last} is {decision}') + + +# Example 7 +import contextlib +import math + +class Client(ConnectionBase): + def __init__(self, *args): + super().__init__(*args) + self._clear_state() + + def _clear_state(self): + self.secret = None + self.last_distance = None + + +# Example 8 + @contextlib.contextmanager + def session(self, lower, upper, secret): + print(f'Guess a number between {lower} and {upper}!' + f' Shhhhh, it\'s {secret}.') + self.secret = secret + self.send(f'PARAMS {lower} {upper}') + try: + yield + finally: + self._clear_state() + self.send('PARAMS 0 -1') + + +# Example 9 + def request_numbers(self, count): + for _ in range(count): + self.send('NUMBER') + data = self.receive() + yield int(data) + if self.last_distance == 0: + return + + +# Example 10 + def report_outcome(self, number): + new_distance = math.fabs(number - self.secret) + decision = UNSURE + + if new_distance == 0: + decision = CORRECT + elif self.last_distance is None: + pass + elif new_distance < self.last_distance: + decision = WARMER + elif new_distance > self.last_distance: + decision = COLDER + + self.last_distance = new_distance + + self.send(f'REPORT {decision}') + return decision + + +# Example 11 +import socket +from threading import Thread + +def handle_connection(connection): + with connection: + session = Session(connection) + try: + session.loop() + except EOFError: + pass + +def run_server(address): + with socket.socket() as listener: + # Allow the port to be reused + listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + listener.bind(address) + listener.listen() + while True: + connection, _ = listener.accept() + thread = Thread(target=handle_connection, + args=(connection,), + daemon=True) + thread.start() + + +# Example 12 +def run_client(address): + with socket.create_connection(address) as connection: + client = Client(connection) + + with client.session(1, 5, 3): + results = [(x, client.report_outcome(x)) + for x in client.request_numbers(5)] + + with client.session(10, 15, 12): + for number in client.request_numbers(5): + outcome = client.report_outcome(number) + results.append((number, outcome)) + + return results + + +# Example 13 +def main(): + address = ('127.0.0.1', 1234) + server_thread = Thread( + target=run_server, args=(address,), daemon=True) + server_thread.start() + + results = run_client(address) + for number, outcome in results: + print(f'Client: {number} is {outcome}') + +main() + + +# Example 14 +class AsyncConnectionBase: + def __init__(self, reader, writer): # Changed + self.reader = reader # Changed + self.writer = writer # Changed + + async def send(self, command): + line = command + '\n' + data = line.encode() + self.writer.write(data) # Changed + await self.writer.drain() # Changed + + async def receive(self): + line = await self.reader.readline() # Changed + if not line: + raise EOFError('Connection closed') + return line[:-1].decode() + + +# Example 15 +class AsyncSession(AsyncConnectionBase): # Changed + def __init__(self, *args): + super().__init__(*args) + self._clear_values(None, None) + + def _clear_values(self, lower, upper): + self.lower = lower + self.upper = upper + self.secret = None + self.guesses = [] + + +# Example 16 + async def loop(self): # Changed + while command := await self.receive(): # Changed + parts = command.split(' ') + if parts[0] == 'PARAMS': + self.set_params(parts) + elif parts[0] == 'NUMBER': + await self.send_number() # Changed + elif parts[0] == 'REPORT': + self.receive_report(parts) + else: + raise UnknownCommandError(command) + + +# Example 17 + def set_params(self, parts): + assert len(parts) == 3 + lower = int(parts[1]) + upper = int(parts[2]) + self._clear_values(lower, upper) + + +# Example 18 + def next_guess(self): + if self.secret is not None: + return self.secret + + while True: + guess = random.randint(self.lower, self.upper) + if guess not in self.guesses: + return guess + + async def send_number(self): # Changed + guess = self.next_guess() + self.guesses.append(guess) + await self.send(format(guess)) # Changed + + +# Example 19 + def receive_report(self, parts): + assert len(parts) == 2 + decision = parts[1] + + last = self.guesses[-1] + if decision == CORRECT: + self.secret = last + + print(f'Server: {last} is {decision}') + + +# Example 20 +class AsyncClient(AsyncConnectionBase): # Changed + def __init__(self, *args): + super().__init__(*args) + self._clear_state() + + def _clear_state(self): + self.secret = None + self.last_distance = None + + +# Example 21 + @contextlib.asynccontextmanager # Changed + async def session(self, lower, upper, secret): # Changed + print(f'Guess a number between {lower} and {upper}!' + f' Shhhhh, it\'s {secret}.') + self.secret = secret + await self.send(f'PARAMS {lower} {upper}') # Changed + try: + yield + finally: + self._clear_state() + await self.send('PARAMS 0 -1') # Changed + + +# Example 22 + async def request_numbers(self, count): # Changed + for _ in range(count): + await self.send('NUMBER') # Changed + data = await self.receive() # Changed + yield int(data) + if self.last_distance == 0: + return + + +# Example 23 + async def report_outcome(self, number): # Changed + new_distance = math.fabs(number - self.secret) + decision = UNSURE + + if new_distance == 0: + decision = CORRECT + elif self.last_distance is None: + pass + elif new_distance < self.last_distance: + decision = WARMER + elif new_distance > self.last_distance: + decision = COLDER + + self.last_distance = new_distance + + await self.send(f'REPORT {decision}') # Changed + # Make it so the output printing is in + # the same order as the threaded version. + await asyncio.sleep(0.01) + return decision + + +# Example 24 +import asyncio + +async def handle_async_connection(reader, writer): + session = AsyncSession(reader, writer) + try: + await session.loop() + except EOFError: + pass + +async def run_async_server(address): + server = await asyncio.start_server( + handle_async_connection, *address) + async with server: + await server.serve_forever() + + +# Example 25 +async def run_async_client(address): + # Wait for the server to listen before trying to connect + await asyncio.sleep(0.1) + + streams = await asyncio.open_connection(*address) # New + client = AsyncClient(*streams) # New + + async with client.session(1, 5, 3): + results = [(x, await client.report_outcome(x)) + async for x in client.request_numbers(5)] + + async with client.session(10, 15, 12): + async for number in client.request_numbers(5): + outcome = await client.report_outcome(number) + results.append((number, outcome)) + + _, writer = streams # New + writer.close() # New + await writer.wait_closed() # New + + return results + + +# Example 26 +async def main_async(): + address = ('127.0.0.1', 4321) + + server = run_async_server(address) + asyncio.create_task(server) + + results = await run_async_client(address) + for number, outcome in results: + print(f'Client: {number} is {outcome}') + +logging.getLogger().setLevel(logging.ERROR) + +asyncio.run(main_async()) + +logging.getLogger().setLevel(logging.DEBUG) diff --git a/example_code/item_62.py b/example_code/item_62.py new file mode 100755 index 0000000..d94a1f4 --- /dev/null +++ b/example_code/item_62.py @@ -0,0 +1,295 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class NoNewData(Exception): + pass + +def readline(handle): + offset = handle.tell() + handle.seek(0, 2) + length = handle.tell() + + if length == offset: + raise NoNewData + + handle.seek(offset, 0) + return handle.readline() + + +# Example 2 +import time + +def tail_file(handle, interval, write_func): + while not handle.closed: + try: + line = readline(handle) + except NoNewData: + time.sleep(interval) + else: + write_func(line) + + +# Example 3 +from threading import Lock, Thread + +def run_threads(handles, interval, output_path): + with open(output_path, 'wb') as output: + lock = Lock() + def write(data): + with lock: + output.write(data) + + threads = [] + for handle in handles: + args = (handle, interval, write) + thread = Thread(target=tail_file, args=args) + thread.start() + threads.append(thread) + + for thread in threads: + thread.join() + + +# Example 4 +# This is all code to simulate the writers to the handles +import collections +import os +import random +import string +from tempfile import TemporaryDirectory + +def write_random_data(path, write_count, interval): + with open(path, 'wb') as f: + for i in range(write_count): + time.sleep(random.random() * interval) + letters = random.choices( + string.ascii_lowercase, k=10) + data = f'{path}-{i:02}-{"".join(letters)}\n' + f.write(data.encode()) + f.flush() + +def start_write_threads(directory, file_count): + paths = [] + for i in range(file_count): + path = os.path.join(directory, str(i)) + with open(path, 'w'): + # Make sure the file at this path will exist when + # the reading thread tries to poll it. + pass + paths.append(path) + args = (path, 10, 0.1) + thread = Thread(target=write_random_data, args=args) + thread.start() + return paths + +def close_all(handles): + time.sleep(1) + for handle in handles: + handle.close() + +def setup(): + tmpdir = TemporaryDirectory() + input_paths = start_write_threads(tmpdir.name, 5) + + handles = [] + for path in input_paths: + handle = open(path, 'rb') + handles.append(handle) + + Thread(target=close_all, args=(handles,)).start() + + output_path = os.path.join(tmpdir.name, 'merged') + return tmpdir, input_paths, handles, output_path + + +# Example 5 +def confirm_merge(input_paths, output_path): + found = collections.defaultdict(list) + with open(output_path, 'rb') as f: + for line in f: + for path in input_paths: + if line.find(path.encode()) == 0: + found[path].append(line) + + expected = collections.defaultdict(list) + for path in input_paths: + with open(path, 'rb') as f: + expected[path].extend(f.readlines()) + + for key, expected_lines in expected.items(): + found_lines = found[key] + assert expected_lines == found_lines, \ + f'{expected_lines!r} == {found_lines!r}' + +input_paths = ... +handles = ... +output_path = ... + +tmpdir, input_paths, handles, output_path = setup() + +run_threads(handles, 0.1, output_path) + +confirm_merge(input_paths, output_path) + +tmpdir.cleanup() + + +# Example 6 +import asyncio + +# On Windows, a ProactorEventLoop can't be created within +# threads because it tries to register signal handlers. This +# is a work-around to always use the SelectorEventLoop policy +# instead. See: https://bugs.python.org/issue33792 +policy = asyncio.get_event_loop_policy() +policy._loop_factory = asyncio.SelectorEventLoop + +async def run_tasks_mixed(handles, interval, output_path): + loop = asyncio.get_event_loop() + + with open(output_path, 'wb') as output: + async def write_async(data): + output.write(data) + + def write(data): + coro = write_async(data) + future = asyncio.run_coroutine_threadsafe( + coro, loop) + future.result() + + tasks = [] + for handle in handles: + task = loop.run_in_executor( + None, tail_file, handle, interval, write) + tasks.append(task) + + await asyncio.gather(*tasks) + + +# Example 7 +input_paths = ... +handles = ... +output_path = ... + +tmpdir, input_paths, handles, output_path = setup() + +asyncio.run(run_tasks_mixed(handles, 0.1, output_path)) + +confirm_merge(input_paths, output_path) + +tmpdir.cleanup() + + +# Example 8 +async def tail_async(handle, interval, write_func): + loop = asyncio.get_event_loop() + + while not handle.closed: + try: + line = await loop.run_in_executor( + None, readline, handle) + except NoNewData: + await asyncio.sleep(interval) + else: + await write_func(line) + + +# Example 9 +async def run_tasks(handles, interval, output_path): + with open(output_path, 'wb') as output: + async def write_async(data): + output.write(data) + + tasks = [] + for handle in handles: + coro = tail_async(handle, interval, write_async) + task = asyncio.create_task(coro) + tasks.append(task) + + await asyncio.gather(*tasks) + + +# Example 10 +input_paths = ... +handles = ... +output_path = ... + +tmpdir, input_paths, handles, output_path = setup() + +asyncio.run(run_tasks(handles, 0.1, output_path)) + +confirm_merge(input_paths, output_path) + +tmpdir.cleanup() + + +# Example 11 +def tail_file(handle, interval, write_func): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + async def write_async(data): + write_func(data) + + coro = tail_async(handle, interval, write_async) + loop.run_until_complete(coro) + + +# Example 12 +input_paths = ... +handles = ... +output_path = ... + +tmpdir, input_paths, handles, output_path = setup() + +run_threads(handles, 0.1, output_path) + +confirm_merge(input_paths, output_path) + +tmpdir.cleanup() diff --git a/example_code/item_63.py b/example_code/item_63.py new file mode 100755 index 0000000..5a56e96 --- /dev/null +++ b/example_code/item_63.py @@ -0,0 +1,252 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +import asyncio + +# On Windows, a ProactorEventLoop can't be created within +# threads because it tries to register signal handlers. This +# is a work-around to always use the SelectorEventLoop policy +# instead. See: https://bugs.python.org/issue33792 +policy = asyncio.get_event_loop_policy() +policy._loop_factory = asyncio.SelectorEventLoop + +async def run_tasks(handles, interval, output_path): + with open(output_path, 'wb') as output: + async def write_async(data): + output.write(data) + + tasks = [] + for handle in handles: + coro = tail_async(handle, interval, write_async) + task = asyncio.create_task(coro) + tasks.append(task) + + await asyncio.gather(*tasks) + + +# Example 2 +import time + +async def slow_coroutine(): + time.sleep(0.5) # Simulating slow I/O + +asyncio.run(slow_coroutine(), debug=True) + + +# Example 3 +from threading import Thread + +class WriteThread(Thread): + def __init__(self, output_path): + super().__init__() + self.output_path = output_path + self.output = None + self.loop = asyncio.new_event_loop() + + def run(self): + asyncio.set_event_loop(self.loop) + with open(self.output_path, 'wb') as self.output: + self.loop.run_forever() + + # Run one final round of callbacks so the await on + # stop() in another event loop will be resolved. + self.loop.run_until_complete(asyncio.sleep(0)) + + +# Example 4 + async def real_write(self, data): + self.output.write(data) + + async def write(self, data): + coro = self.real_write(data) + future = asyncio.run_coroutine_threadsafe( + coro, self.loop) + await asyncio.wrap_future(future) + + +# Example 5 + async def real_stop(self): + self.loop.stop() + + async def stop(self): + coro = self.real_stop() + future = asyncio.run_coroutine_threadsafe( + coro, self.loop) + await asyncio.wrap_future(future) + + +# Example 6 + async def __aenter__(self): + loop = asyncio.get_event_loop() + await loop.run_in_executor(None, self.start) + return self + + async def __aexit__(self, *_): + await self.stop() + + +# Example 7 +class NoNewData(Exception): + pass + +def readline(handle): + offset = handle.tell() + handle.seek(0, 2) + length = handle.tell() + + if length == offset: + raise NoNewData + + handle.seek(offset, 0) + return handle.readline() + +async def tail_async(handle, interval, write_func): + loop = asyncio.get_event_loop() + + while not handle.closed: + try: + line = await loop.run_in_executor( + None, readline, handle) + except NoNewData: + await asyncio.sleep(interval) + else: + await write_func(line) + +async def run_fully_async(handles, interval, output_path): + async with WriteThread(output_path) as output: + tasks = [] + for handle in handles: + coro = tail_async(handle, interval, output.write) + task = asyncio.create_task(coro) + tasks.append(task) + + await asyncio.gather(*tasks) + + +# Example 8 +# This is all code to simulate the writers to the handles +import collections +import os +import random +import string +from tempfile import TemporaryDirectory + +def write_random_data(path, write_count, interval): + with open(path, 'wb') as f: + for i in range(write_count): + time.sleep(random.random() * interval) + letters = random.choices( + string.ascii_lowercase, k=10) + data = f'{path}-{i:02}-{"".join(letters)}\n' + f.write(data.encode()) + f.flush() + +def start_write_threads(directory, file_count): + paths = [] + for i in range(file_count): + path = os.path.join(directory, str(i)) + with open(path, 'w'): + # Make sure the file at this path will exist when + # the reading thread tries to poll it. + pass + paths.append(path) + args = (path, 10, 0.1) + thread = Thread(target=write_random_data, args=args) + thread.start() + return paths + +def close_all(handles): + time.sleep(1) + for handle in handles: + handle.close() + +def setup(): + tmpdir = TemporaryDirectory() + input_paths = start_write_threads(tmpdir.name, 5) + + handles = [] + for path in input_paths: + handle = open(path, 'rb') + handles.append(handle) + + Thread(target=close_all, args=(handles,)).start() + + output_path = os.path.join(tmpdir.name, 'merged') + return tmpdir, input_paths, handles, output_path + + +# Example 9 +def confirm_merge(input_paths, output_path): + found = collections.defaultdict(list) + with open(output_path, 'rb') as f: + for line in f: + for path in input_paths: + if line.find(path.encode()) == 0: + found[path].append(line) + + expected = collections.defaultdict(list) + for path in input_paths: + with open(path, 'rb') as f: + expected[path].extend(f.readlines()) + + for key, expected_lines in expected.items(): + found_lines = found[key] + assert expected_lines == found_lines + +input_paths = ... +handles = ... +output_path = ... + +tmpdir, input_paths, handles, output_path = setup() + +asyncio.run(run_fully_async(handles, 0.1, output_path)) + +confirm_merge(input_paths, output_path) + +tmpdir.cleanup() diff --git a/example_code/item_64/parallel/my_module.py b/example_code/item_64/parallel/my_module.py new file mode 100755 index 0000000..1027356 --- /dev/null +++ b/example_code/item_64/parallel/my_module.py @@ -0,0 +1,23 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def gcd(pair): + a, b = pair + low = min(a, b) + for i in range(low, 0, -1): + if a % i == 0 and b % i == 0: + return i + assert False, 'Not reachable' diff --git a/example_code/item_64/parallel/run_parallel.py b/example_code/item_64/parallel/run_parallel.py new file mode 100755 index 0000000..92f6d46 --- /dev/null +++ b/example_code/item_64/parallel/run_parallel.py @@ -0,0 +1,38 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import my_module +from concurrent.futures import ProcessPoolExecutor +import time + +NUMBERS = [ + (1963309, 2265973), (2030677, 3814172), + (1551645, 2229620), (2039045, 2020802), + (1823712, 1924928), (2293129, 1020491), + (1281238, 2273782), (3823812, 4237281), + (3812741, 4729139), (1292391, 2123811), +] + +def main(): + start = time.time() + pool = ProcessPoolExecutor(max_workers=2) # The one change + results = list(pool.map(my_module.gcd, NUMBERS)) + end = time.time() + delta = end - start + print(f'Took {delta:.3f} seconds') + +if __name__ == '__main__': + main() diff --git a/example_code/item_64/parallel/run_serial.py b/example_code/item_64/parallel/run_serial.py new file mode 100755 index 0000000..4188579 --- /dev/null +++ b/example_code/item_64/parallel/run_serial.py @@ -0,0 +1,36 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import my_module +import time + +NUMBERS = [ + (1963309, 2265973), (2030677, 3814172), + (1551645, 2229620), (2039045, 2020802), + (1823712, 1924928), (2293129, 1020491), + (1281238, 2273782), (3823812, 4237281), + (3812741, 4729139), (1292391, 2123811), +] + +def main(): + start = time.time() + results = list(map(my_module.gcd, NUMBERS)) + end = time.time() + delta = end - start + print(f'Took {delta:.3f} seconds') + +if __name__ == '__main__': + main() diff --git a/example_code/item_64/parallel/run_threads.py b/example_code/item_64/parallel/run_threads.py new file mode 100755 index 0000000..e89e78b --- /dev/null +++ b/example_code/item_64/parallel/run_threads.py @@ -0,0 +1,38 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import my_module +from concurrent.futures import ThreadPoolExecutor +import time + +NUMBERS = [ + (1963309, 2265973), (2030677, 3814172), + (1551645, 2229620), (2039045, 2020802), + (1823712, 1924928), (2293129, 1020491), + (1281238, 2273782), (3823812, 4237281), + (3812741, 4729139), (1292391, 2123811), +] + +def main(): + start = time.time() + pool = ThreadPoolExecutor(max_workers=2) + results = list(pool.map(my_module.gcd, NUMBERS)) + end = time.time() + delta = end - start + print(f'Took {delta:.3f} seconds') + +if __name__ == '__main__': + main() diff --git a/example_code/item_65.py b/example_code/item_65.py new file mode 100755 index 0000000..8929b87 --- /dev/null +++ b/example_code/item_65.py @@ -0,0 +1,197 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +def try_finally_example(filename): + print('* Opening file') + handle = open(filename, encoding='utf-8') # May raise OSError + try: + print('* Reading data') + return handle.read() # May raise UnicodeDecodeError + finally: + print('* Calling close()') + handle.close() # Always runs after try block + + +# Example 2 +try: + filename = 'random_data.txt' + + with open(filename, 'wb') as f: + f.write(b'\xf1\xf2\xf3\xf4\xf5') # Invalid utf-8 + + data = try_finally_example(filename) + # This should not be reached. + import sys + sys.exit(1) +except: + logging.exception('Expected') +else: + assert False + + +# Example 3 +try: + try_finally_example('does_not_exist.txt') +except: + logging.exception('Expected') +else: + assert False + + +# Example 4 +import json + +def load_json_key(data, key): + try: + print('* Loading JSON data') + result_dict = json.loads(data) # May raise ValueError + except ValueError as e: + print('* Handling ValueError') + raise KeyError(key) from e + else: + print('* Looking up key') + return result_dict[key] # May raise KeyError + + +# Example 5 +assert load_json_key('{"foo": "bar"}', 'foo') == 'bar' + + +# Example 6 +try: + load_json_key('{"foo": bad payload', 'foo') +except: + logging.exception('Expected') +else: + assert False + + +# Example 7 +try: + load_json_key('{"foo": "bar"}', 'does not exist') +except: + logging.exception('Expected') +else: + assert False + + +# Example 8 +UNDEFINED = object() +DIE_IN_ELSE_BLOCK = False + +def divide_json(path): + print('* Opening file') + handle = open(path, 'r+') # May raise OSError + try: + print('* Reading data') + data = handle.read() # May raise UnicodeDecodeError + print('* Loading JSON data') + op = json.loads(data) # May raise ValueError + print('* Performing calculation') + value = ( + op['numerator'] / + op['denominator']) # May raise ZeroDivisionError + except ZeroDivisionError as e: + print('* Handling ZeroDivisionError') + return UNDEFINED + else: + print('* Writing calculation') + op['result'] = value + result = json.dumps(op) + handle.seek(0) # May raise OSError + if DIE_IN_ELSE_BLOCK: + import errno + import os + raise OSError(errno.ENOSPC, os.strerror(errno.ENOSPC)) + handle.write(result) # May raise OSError + return value + finally: + print('* Calling close()') + handle.close() # Always runs + + +# Example 9 +temp_path = 'random_data.json' + +with open(temp_path, 'w') as f: + f.write('{"numerator": 1, "denominator": 10}') + +assert divide_json(temp_path) == 0.1 + + +# Example 10 +with open(temp_path, 'w') as f: + f.write('{"numerator": 1, "denominator": 0}') + +assert divide_json(temp_path) is UNDEFINED + + +# Example 11 +try: + with open(temp_path, 'w') as f: + f.write('{"numerator": 1 bad data') + + divide_json(temp_path) +except: + logging.exception('Expected') +else: + assert False + + +# Example 12 +try: + with open(temp_path, 'w') as f: + f.write('{"numerator": 1, "denominator": 10}') + DIE_IN_ELSE_BLOCK = True + + divide_json(temp_path) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_66.py b/example_code/item_66.py new file mode 100755 index 0000000..213d70a --- /dev/null +++ b/example_code/item_66.py @@ -0,0 +1,136 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +from threading import Lock + +lock = Lock() +with lock: + # Do something while maintaining an invariant + pass + + +# Example 2 +lock.acquire() +try: + # Do something while maintaining an invariant + pass +finally: + lock.release() + + +# Example 3 +import logging +logging.getLogger().setLevel(logging.WARNING) + +def my_function(): + logging.debug('Some debug data') + logging.error('Error log here') + logging.debug('More debug data') + + +# Example 4 +my_function() + + +# Example 5 +from contextlib import contextmanager + +@contextmanager +def debug_logging(level): + logger = logging.getLogger() + old_level = logger.getEffectiveLevel() + logger.setLevel(level) + try: + yield + finally: + logger.setLevel(old_level) + + +# Example 6 +with debug_logging(logging.DEBUG): + print('* Inside:') + my_function() + +print('* After:') +my_function() + + +# Example 7 +with open('my_output.txt', 'w') as handle: + handle.write('This is some data!') + + +# Example 8 +@contextmanager +def log_level(level, name): + logger = logging.getLogger(name) + old_level = logger.getEffectiveLevel() + logger.setLevel(level) + try: + yield logger + finally: + logger.setLevel(old_level) + + +# Example 9 +with log_level(logging.DEBUG, 'my-log') as logger: + logger.debug(f'This is a message for {logger.name}!') + logging.debug('This will not print') + + +# Example 10 +logger = logging.getLogger('my-log') +logger.debug('Debug will not print') +logger.error('Error will print') + + +# Example 11 +with log_level(logging.DEBUG, 'other-log') as logger: + logger.debug(f'This is a message for {logger.name}!') + logging.debug('This will not print') diff --git a/example_code/item_67.py b/example_code/item_67.py new file mode 100755 index 0000000..1a75166 --- /dev/null +++ b/example_code/item_67.py @@ -0,0 +1,125 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +import time + +now = 1552774475 +local_tuple = time.localtime(now) +time_format = '%Y-%m-%d %H:%M:%S' +time_str = time.strftime(time_format, local_tuple) +print(time_str) + + +# Example 2 +time_tuple = time.strptime(time_str, time_format) +utc_now = time.mktime(time_tuple) +print(utc_now) + + +# Example 3 +import os + +if os.name == 'nt': + print("This example doesn't work on Windows") +else: + parse_format = '%Y-%m-%d %H:%M:%S %Z' + depart_sfo = '2019-03-16 15:45:16 PDT' + time_tuple = time.strptime(depart_sfo, parse_format) + time_str = time.strftime(time_format, time_tuple) + print(time_str) + + +# Example 4 +try: + arrival_nyc = '2019-03-16 23:33:24 EDT' + time_tuple = time.strptime(arrival_nyc, time_format) +except: + logging.exception('Expected') +else: + assert False + + +# Example 5 +from datetime import datetime, timezone + +now = datetime(2019, 3, 16, 22, 14, 35) +now_utc = now.replace(tzinfo=timezone.utc) +now_local = now_utc.astimezone() +print(now_local) + + +# Example 6 +time_str = '2019-03-16 15:14:35' +now = datetime.strptime(time_str, time_format) +time_tuple = now.timetuple() +utc_now = time.mktime(time_tuple) +print(utc_now) + + +# Example 7 +import pytz + +arrival_nyc = '2019-03-16 23:33:24' +nyc_dt_naive = datetime.strptime(arrival_nyc, time_format) +eastern = pytz.timezone('US/Eastern') +nyc_dt = eastern.localize(nyc_dt_naive) +utc_dt = pytz.utc.normalize(nyc_dt.astimezone(pytz.utc)) +print(utc_dt) + + +# Example 8 +pacific = pytz.timezone('US/Pacific') +sf_dt = pacific.normalize(utc_dt.astimezone(pacific)) +print(sf_dt) + + +# Example 9 +nepal = pytz.timezone('Asia/Katmandu') +nepal_dt = nepal.normalize(utc_dt.astimezone(nepal)) +print(nepal_dt) diff --git a/example_code/item_68.py b/example_code/item_68.py new file mode 100755 index 0000000..4b9eb5f --- /dev/null +++ b/example_code/item_68.py @@ -0,0 +1,224 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class GameState: + def __init__(self): + self.level = 0 + self.lives = 4 + + +# Example 2 +state = GameState() +state.level += 1 # Player beat a level +state.lives -= 1 # Player had to try again + +print(state.__dict__) + + +# Example 3 +import pickle + +state_path = 'game_state.bin' +with open(state_path, 'wb') as f: + pickle.dump(state, f) + + +# Example 4 +with open(state_path, 'rb') as f: + state_after = pickle.load(f) + +print(state_after.__dict__) + + +# Example 5 +class GameState: + def __init__(self): + self.level = 0 + self.lives = 4 + self.points = 0 # New field + + +# Example 6 +state = GameState() +serialized = pickle.dumps(state) +state_after = pickle.loads(serialized) +print(state_after.__dict__) + + +# Example 7 +with open(state_path, 'rb') as f: + state_after = pickle.load(f) + +print(state_after.__dict__) + + +# Example 8 +assert isinstance(state_after, GameState) + + +# Example 9 +class GameState: + def __init__(self, level=0, lives=4, points=0): + self.level = level + self.lives = lives + self.points = points + + +# Example 10 +def pickle_game_state(game_state): + kwargs = game_state.__dict__ + return unpickle_game_state, (kwargs,) + + +# Example 11 +def unpickle_game_state(kwargs): + return GameState(**kwargs) + + +# Example 12 +import copyreg + +copyreg.pickle(GameState, pickle_game_state) + + +# Example 13 +state = GameState() +state.points += 1000 +serialized = pickle.dumps(state) +state_after = pickle.loads(serialized) +print(state_after.__dict__) + + +# Example 14 +class GameState: + def __init__(self, level=0, lives=4, points=0, magic=5): + self.level = level + self.lives = lives + self.points = points + self.magic = magic # New field + + +# Example 15 +print('Before:', state.__dict__) +state_after = pickle.loads(serialized) +print('After: ', state_after.__dict__) + + +# Example 16 +class GameState: + def __init__(self, level=0, points=0, magic=5): + self.level = level + self.points = points + self.magic = magic + + +# Example 17 +try: + pickle.loads(serialized) +except: + logging.exception('Expected') +else: + assert False + + +# Example 18 +def pickle_game_state(game_state): + kwargs = game_state.__dict__ + kwargs['version'] = 2 + return unpickle_game_state, (kwargs,) + + +# Example 19 +def unpickle_game_state(kwargs): + version = kwargs.pop('version', 1) + if version == 1: + del kwargs['lives'] + return GameState(**kwargs) + + +# Example 20 +copyreg.pickle(GameState, pickle_game_state) +print('Before:', state.__dict__) +state_after = pickle.loads(serialized) +print('After: ', state_after.__dict__) + + +# Example 21 +copyreg.dispatch_table.clear() +state = GameState() +serialized = pickle.dumps(state) +del GameState +class BetterGameState: + def __init__(self, level=0, points=0, magic=5): + self.level = level + self.points = points + self.magic = magic + + +# Example 22 +try: + pickle.loads(serialized) +except: + logging.exception('Expected') +else: + assert False + + +# Example 23 +print(serialized) + + +# Example 24 +copyreg.pickle(BetterGameState, pickle_game_state) + + +# Example 25 +state = BetterGameState() +serialized = pickle.dumps(state) +print(serialized) diff --git a/example_code/item_69.py b/example_code/item_69.py new file mode 100755 index 0000000..1e18676 --- /dev/null +++ b/example_code/item_69.py @@ -0,0 +1,99 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +rate = 1.45 +seconds = 3*60 + 42 +cost = rate * seconds / 60 +print(cost) + + +# Example 2 +print(round(cost, 2)) + + +# Example 3 +from decimal import Decimal + +rate = Decimal('1.45') +seconds = Decimal(3*60 + 42) +cost = rate * seconds / Decimal(60) +print(cost) + + +# Example 4 +print(Decimal('1.45')) +print(Decimal(1.45)) + + +# Example 5 +print('456') +print(456) + + +# Example 6 +rate = Decimal('0.05') +seconds = Decimal('5') +small_cost = rate * seconds / Decimal(60) +print(small_cost) + + +# Example 7 +print(round(small_cost, 2)) + + +# Example 8 +from decimal import ROUND_UP + +rounded = cost.quantize(Decimal('0.01'), rounding=ROUND_UP) +print(f'Rounded {cost} to {rounded}') + + +# Example 9 +rounded = small_cost.quantize(Decimal('0.01'), rounding=ROUND_UP) +print(f'Rounded {small_cost} to {rounded}') diff --git a/example_code/item_70.py b/example_code/item_70.py new file mode 100755 index 0000000..3775662 --- /dev/null +++ b/example_code/item_70.py @@ -0,0 +1,141 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +def insertion_sort(data): + result = [] + for value in data: + insert_value(result, value) + return result + + +# Example 2 +def insert_value(array, value): + for i, existing in enumerate(array): + if existing > value: + array.insert(i, value) + return + array.append(value) + + +# Example 3 +from random import randint + +max_size = 10**4 +data = [randint(0, max_size) for _ in range(max_size)] +test = lambda: insertion_sort(data) + + +# Example 4 +from cProfile import Profile + +profiler = Profile() +profiler.runcall(test) + + +# Example 5 +from pstats import Stats + +stats = Stats(profiler) +stats = Stats(profiler, stream=STDOUT) +stats.strip_dirs() +stats.sort_stats('cumulative') +stats.print_stats() + + +# Example 6 +from bisect import bisect_left + +def insert_value(array, value): + i = bisect_left(array, value) + array.insert(i, value) + + +# Example 7 +profiler = Profile() +profiler.runcall(test) +stats = Stats(profiler, stream=STDOUT) +stats.strip_dirs() +stats.sort_stats('cumulative') +stats.print_stats() + + +# Example 8 +def my_utility(a, b): + c = 1 + for i in range(100): + c += a * b + +def first_func(): + for _ in range(1000): + my_utility(4, 5) + +def second_func(): + for _ in range(10): + my_utility(1, 3) + +def my_program(): + for _ in range(20): + first_func() + second_func() + + +# Example 9 +profiler = Profile() +profiler.runcall(my_program) +stats = Stats(profiler, stream=STDOUT) +stats.strip_dirs() +stats.sort_stats('cumulative') +stats.print_stats() + + +# Example 10 +stats = Stats(profiler, stream=STDOUT) +stats.strip_dirs() +stats.sort_stats('cumulative') +stats.print_callers() diff --git a/example_code/item_71.py b/example_code/item_71.py new file mode 100755 index 0000000..bf16a0a --- /dev/null +++ b/example_code/item_71.py @@ -0,0 +1,264 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class Email: + def __init__(self, sender, receiver, message): + self.sender = sender + self.receiver = receiver + self.message = message + + +# Example 2 +def get_emails(): + yield Email('foo@example.com', 'bar@example.com', 'hello1') + yield Email('baz@example.com', 'banana@example.com', 'hello2') + yield None + yield Email('meep@example.com', 'butter@example.com', 'hello3') + yield Email('stuff@example.com', 'avocado@example.com', 'hello4') + yield None + yield Email('thingy@example.com', 'orange@example.com', 'hello5') + yield Email('roger@example.com', 'bob@example.com', 'hello6') + yield None + yield Email('peanut@example.com', 'alice@example.com', 'hello7') + yield None + +EMAIL_IT = get_emails() + +class NoEmailError(Exception): + pass + +def try_receive_email(): + # Returns an Email instance or raises NoEmailError + try: + email = next(EMAIL_IT) + except StopIteration: + email = None + + if not email: + raise NoEmailError + + print(f'Produced email: {email.message}') + return email + + +# Example 3 +def produce_emails(queue): + while True: + try: + email = try_receive_email() + except NoEmailError: + return + else: + queue.append(email) # Producer + + +# Example 4 +def consume_one_email(queue): + if not queue: + return + email = queue.pop(0) # Consumer + # Index the message for long-term archival + print(f'Consumed email: {email.message}') + + +# Example 5 +def loop(queue, keep_running): + while keep_running(): + produce_emails(queue) + consume_one_email(queue) + +def make_test_end(): + count=list(range(10)) + + def func(): + if count: + count.pop() + return True + return False + + return func + + +def my_end_func(): + pass + +my_end_func = make_test_end() +loop([], my_end_func) + + +# Example 6 +import timeit + +def print_results(count, tests): + avg_iteration = sum(tests) / len(tests) + print(f'Count {count:>5,} takes {avg_iteration:.6f}s') + return count, avg_iteration + +def list_append_benchmark(count): + def run(queue): + for i in range(count): + queue.append(i) + + tests = timeit.repeat( + setup='queue = []', + stmt='run(queue)', + globals=locals(), + repeat=1000, + number=1) + + return print_results(count, tests) + + +# Example 7 +def print_delta(before, after): + before_count, before_time = before + after_count, after_time = after + growth = 1 + (after_count - before_count) / before_count + slowdown = 1 + (after_time - before_time) / before_time + print(f'{growth:>4.1f}x data size, {slowdown:>4.1f}x time') + +baseline = list_append_benchmark(500) +for count in (1_000, 2_000, 3_000, 4_000, 5_000): + print() + comparison = list_append_benchmark(count) + print_delta(baseline, comparison) + + +# Example 8 +def list_pop_benchmark(count): + def prepare(): + return list(range(count)) + + def run(queue): + while queue: + queue.pop(0) + + tests = timeit.repeat( + setup='queue = prepare()', + stmt='run(queue)', + globals=locals(), + repeat=1000, + number=1) + + return print_results(count, tests) + + +# Example 9 +baseline = list_pop_benchmark(500) +for count in (1_000, 2_000, 3_000, 4_000, 5_000): + print() + comparison = list_pop_benchmark(count) + print_delta(baseline, comparison) + + +# Example 10 +import collections + +def consume_one_email(queue): + if not queue: + return + email = queue.popleft() # Consumer + # Process the email message + print(f'Consumed email: {email.message}') + +def my_end_func(): + pass + +my_end_func = make_test_end() +EMAIL_IT = get_emails() +loop(collections.deque(), my_end_func) + + +# Example 11 +def deque_append_benchmark(count): + def prepare(): + return collections.deque() + + def run(queue): + for i in range(count): + queue.append(i) + + tests = timeit.repeat( + setup='queue = prepare()', + stmt='run(queue)', + globals=locals(), + repeat=1000, + number=1) + return print_results(count, tests) + +baseline = deque_append_benchmark(500) +for count in (1_000, 2_000, 3_000, 4_000, 5_000): + print() + comparison = deque_append_benchmark(count) + print_delta(baseline, comparison) + + +# Example 12 +def dequeue_popleft_benchmark(count): + def prepare(): + return collections.deque(range(count)) + + def run(queue): + while queue: + queue.popleft() + + tests = timeit.repeat( + setup='queue = prepare()', + stmt='run(queue)', + globals=locals(), + repeat=1000, + number=1) + + return print_results(count, tests) + +baseline = dequeue_popleft_benchmark(500) +for count in (1_000, 2_000, 3_000, 4_000, 5_000): + print() + comparison = dequeue_popleft_benchmark(count) + print_delta(baseline, comparison) diff --git a/example_code/item_72.py b/example_code/item_72.py new file mode 100755 index 0000000..078201a --- /dev/null +++ b/example_code/item_72.py @@ -0,0 +1,115 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +data = list(range(10**5)) +index = data.index(91234) +assert index == 91234 + + +# Example 2 +def find_closest(sequence, goal): + for index, value in enumerate(sequence): + if goal < value: + return index + raise ValueError(f'{goal} is out of bounds') + +index = find_closest(data, 91234.56) +assert index == 91235 + +try: + find_closest(data, 100000000) +except ValueError: + pass # Expected +else: + assert False + + +# Example 3 +from bisect import bisect_left + +index = bisect_left(data, 91234) # Exact match +assert index == 91234 + +index = bisect_left(data, 91234.56) # Closest match +assert index == 91235 + + +# Example 4 +import random +import timeit + +size = 10**5 +iterations = 1000 + +data = list(range(size)) +to_lookup = [random.randint(0, size) + for _ in range(iterations)] + +def run_linear(data, to_lookup): + for index in to_lookup: + data.index(index) + +def run_bisect(data, to_lookup): + for index in to_lookup: + bisect_left(data, index) + +baseline = timeit.timeit( + stmt='run_linear(data, to_lookup)', + globals=globals(), + number=10) +print(f'Linear search takes {baseline:.6f}s') + +comparison = timeit.timeit( + stmt='run_bisect(data, to_lookup)', + globals=globals(), + number=10) +print(f'Bisect search takes {comparison:.6f}s') + +slowdown = 1 + ((baseline - comparison) / comparison) +print(f'{slowdown:.1f}x time') diff --git a/example_code/item_73.py b/example_code/item_73.py new file mode 100755 index 0000000..352d6ac --- /dev/null +++ b/example_code/item_73.py @@ -0,0 +1,384 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class Book: + def __init__(self, title, due_date): + self.title = title + self.due_date = due_date + + +# Example 2 +def add_book(queue, book): + queue.append(book) + queue.sort(key=lambda x: x.due_date, reverse=True) + +queue = [] +add_book(queue, Book('Don Quixote', '2019-06-07')) +add_book(queue, Book('Frankenstein', '2019-06-05')) +add_book(queue, Book('Les Misérables', '2019-06-08')) +add_book(queue, Book('War and Peace', '2019-06-03')) + + +# Example 3 +class NoOverdueBooks(Exception): + pass + +def next_overdue_book(queue, now): + if queue: + book = queue[-1] + if book.due_date < now: + queue.pop() + return book + + raise NoOverdueBooks + + +# Example 4 +now = '2019-06-10' + +found = next_overdue_book(queue, now) +print(found.title) + +found = next_overdue_book(queue, now) +print(found.title) + + +# Example 5 +def return_book(queue, book): + queue.remove(book) + +queue = [] +book = Book('Treasure Island', '2019-06-04') + +add_book(queue, book) +print('Before return:', [x.title for x in queue]) + +return_book(queue, book) +print('After return: ', [x.title for x in queue]) + + +# Example 6 +try: + next_overdue_book(queue, now) +except NoOverdueBooks: + pass # Expected +else: + assert False # Doesn't happen + + +# Example 7 +import random +import timeit + +def print_results(count, tests): + avg_iteration = sum(tests) / len(tests) + print(f'Count {count:>5,} takes {avg_iteration:.6f}s') + return count, avg_iteration + +def print_delta(before, after): + before_count, before_time = before + after_count, after_time = after + growth = 1 + (after_count - before_count) / before_count + slowdown = 1 + (after_time - before_time) / before_time + print(f'{growth:>4.1f}x data size, {slowdown:>4.1f}x time') + +def list_overdue_benchmark(count): + def prepare(): + to_add = list(range(count)) + random.shuffle(to_add) + return [], to_add + + def run(queue, to_add): + for i in to_add: + queue.append(i) + queue.sort(reverse=True) + + while queue: + queue.pop() + + tests = timeit.repeat( + setup='queue, to_add = prepare()', + stmt=f'run(queue, to_add)', + globals=locals(), + repeat=100, + number=1) + + return print_results(count, tests) + + +# Example 8 +baseline = list_overdue_benchmark(500) +for count in (1_000, 1_500, 2_000): + print() + comparison = list_overdue_benchmark(count) + print_delta(baseline, comparison) + + +# Example 9 +def list_return_benchmark(count): + def prepare(): + queue = list(range(count)) + random.shuffle(queue) + + to_return = list(range(count)) + random.shuffle(to_return) + + return queue, to_return + + def run(queue, to_return): + for i in to_return: + queue.remove(i) + + tests = timeit.repeat( + setup='queue, to_return = prepare()', + stmt=f'run(queue, to_return)', + globals=locals(), + repeat=100, + number=1) + + return print_results(count, tests) + + +# Example 10 +baseline = list_return_benchmark(500) +for count in (1_000, 1_500, 2_000): + print() + comparison = list_return_benchmark(count) + print_delta(baseline, comparison) + + +# Example 11 +from heapq import heappush + +def add_book(queue, book): + heappush(queue, book) + + +# Example 12 +try: + queue = [] + add_book(queue, Book('Little Women', '2019-06-05')) + add_book(queue, Book('The Time Machine', '2019-05-30')) +except: + logging.exception('Expected') +else: + assert False + + +# Example 13 +import functools + +@functools.total_ordering +class Book: + def __init__(self, title, due_date): + self.title = title + self.due_date = due_date + + def __lt__(self, other): + return self.due_date < other.due_date + + +# Example 14 +queue = [] +add_book(queue, Book('Pride and Prejudice', '2019-06-01')) +add_book(queue, Book('The Time Machine', '2019-05-30')) +add_book(queue, Book('Crime and Punishment', '2019-06-06')) +add_book(queue, Book('Wuthering Heights', '2019-06-12')) +print([b.title for b in queue]) + + +# Example 15 +queue = [ + Book('Pride and Prejudice', '2019-06-01'), + Book('The Time Machine', '2019-05-30'), + Book('Crime and Punishment', '2019-06-06'), + Book('Wuthering Heights', '2019-06-12'), +] +queue.sort() +print([b.title for b in queue]) + + +# Example 16 +from heapq import heapify + +queue = [ + Book('Pride and Prejudice', '2019-06-01'), + Book('The Time Machine', '2019-05-30'), + Book('Crime and Punishment', '2019-06-06'), + Book('Wuthering Heights', '2019-06-12'), +] +heapify(queue) +print([b.title for b in queue]) + + +# Example 17 +from heapq import heappop + +def next_overdue_book(queue, now): + if queue: + book = queue[0] # Most overdue first + if book.due_date < now: + heappop(queue) # Remove the overdue book + return book + + raise NoOverdueBooks + + +# Example 18 +now = '2019-06-02' + +book = next_overdue_book(queue, now) +print(book.title) + +book = next_overdue_book(queue, now) +print(book.title) + +try: + next_overdue_book(queue, now) +except NoOverdueBooks: + pass # Expected +else: + assert False # Doesn't happen + + +# Example 19 +def heap_overdue_benchmark(count): + def prepare(): + to_add = list(range(count)) + random.shuffle(to_add) + return [], to_add + + def run(queue, to_add): + for i in to_add: + heappush(queue, i) + while queue: + heappop(queue) + + tests = timeit.repeat( + setup='queue, to_add = prepare()', + stmt=f'run(queue, to_add)', + globals=locals(), + repeat=100, + number=1) + + return print_results(count, tests) + + +# Example 20 +baseline = heap_overdue_benchmark(500) +for count in (1_000, 1_500, 2_000): + print() + comparison = heap_overdue_benchmark(count) + print_delta(baseline, comparison) + + +# Example 21 +@functools.total_ordering +class Book: + def __init__(self, title, due_date): + self.title = title + self.due_date = due_date + self.returned = False # New field + + def __lt__(self, other): + return self.due_date < other.due_date + + +# Example 22 +def next_overdue_book(queue, now): + while queue: + book = queue[0] + if book.returned: + heappop(queue) + continue + + if book.due_date < now: + heappop(queue) + return book + + break + + raise NoOverdueBooks + +queue = [] + +book = Book('Pride and Prejudice', '2019-06-01') +add_book(queue, book) + +book = Book('The Time Machine', '2019-05-30') +add_book(queue, book) +book.returned = True + +book = Book('Crime and Punishment', '2019-06-06') +add_book(queue, book) +book.returned = True + +book = Book('Wuthering Heights', '2019-06-12') +add_book(queue, book) + +now = '2019-06-11' + +book = next_overdue_book(queue, now) +assert book.title == 'Pride and Prejudice' + +try: + next_overdue_book(queue, now) +except NoOverdueBooks: + pass # Expected +else: + assert False # Doesn't happen + + +# Example 23 +def return_book(queue, book): + book.returned = True + +assert not book.returned +return_book(queue, book) +assert book.returned diff --git a/example_code/item_74.py b/example_code/item_74.py new file mode 100755 index 0000000..dfdbdc9 --- /dev/null +++ b/example_code/item_74.py @@ -0,0 +1,208 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +def timecode_to_index(video_id, timecode): + return 1234 + # Returns the byte offset in the video data + +def request_chunk(video_id, byte_offset, size): + pass + # Returns size bytes of video_id's data from the offset + +video_id = ... +timecode = '01:09:14:28' +byte_offset = timecode_to_index(video_id, timecode) +size = 20 * 1024 * 1024 +video_data = request_chunk(video_id, byte_offset, size) + + +# Example 2 +class NullSocket: + def __init__(self): + self.handle = open(os.devnull, 'wb') + + def send(self, data): + self.handle.write(data) + +socket = ... # socket connection to client +video_data = ... # bytes containing data for video_id +byte_offset = ... # Requested starting position +size = 20 * 1024 * 1024 # Requested chunk size +import os + +socket = NullSocket() +video_data = 100 * os.urandom(1024 * 1024) +byte_offset = 1234 + +chunk = video_data[byte_offset:byte_offset + size] +socket.send(chunk) + + +# Example 3 +import timeit + +def run_test(): + chunk = video_data[byte_offset:byte_offset + size] + # Call socket.send(chunk), but ignoring for benchmark + +result = timeit.timeit( + stmt='run_test()', + globals=globals(), + number=100) / 100 + +print(f'{result:0.9f} seconds') + + +# Example 4 +data = b'shave and a haircut, two bits' +view = memoryview(data) +chunk = view[12:19] +print(chunk) +print('Size: ', chunk.nbytes) +print('Data in view: ', chunk.tobytes()) +print('Underlying data:', chunk.obj) + + +# Example 5 +video_view = memoryview(video_data) + +def run_test(): + chunk = video_view[byte_offset:byte_offset + size] + # Call socket.send(chunk), but ignoring for benchmark + +result = timeit.timeit( + stmt='run_test()', + globals=globals(), + number=100) / 100 + +print(f'{result:0.9f} seconds') + + +# Example 6 +class FakeSocket: + + def recv(self, size): + return video_view[byte_offset:byte_offset+size] + + def recv_into(self, buffer): + source_data = video_view[byte_offset:byte_offset+size] + buffer[:] = source_data + +socket = ... # socket connection to the client +video_cache = ... # Cache of incoming video stream +byte_offset = ... # Incoming buffer position +size = 1024 * 1024 # Incoming chunk size +socket = FakeSocket() +video_cache = video_data[:] +byte_offset = 1234 + +chunk = socket.recv(size) +video_view = memoryview(video_cache) +before = video_view[:byte_offset] +after = video_view[byte_offset + size:] +new_cache = b''.join([before, chunk, after]) + + +# Example 7 +def run_test(): + chunk = socket.recv(size) + before = video_view[:byte_offset] + after = video_view[byte_offset + size:] + new_cache = b''.join([before, chunk, after]) + +result = timeit.timeit( + stmt='run_test()', + globals=globals(), + number=100) / 100 + +print(f'{result:0.9f} seconds') + + +# Example 8 +try: + my_bytes = b'hello' + my_bytes[0] = b'\x79' +except: + logging.exception('Expected') +else: + assert False + + +# Example 9 +my_array = bytearray(b'hello') +my_array[0] = 0x79 +print(my_array) + + +# Example 10 +my_array = bytearray(b'row, row, row your boat') +my_view = memoryview(my_array) +write_view = my_view[3:13] +write_view[:] = b'-10 bytes-' +print(my_array) + + +# Example 11 +video_array = bytearray(video_cache) +write_view = memoryview(video_array) +chunk = write_view[byte_offset:byte_offset + size] +socket.recv_into(chunk) + + +# Example 12 +def run_test(): + chunk = write_view[byte_offset:byte_offset + size] + socket.recv_into(chunk) + +result = timeit.timeit( + stmt='run_test()', + globals=globals(), + number=100) / 100 + +print(f'{result:0.9f} seconds') diff --git a/example_code/item_75.py b/example_code/item_75.py new file mode 100755 index 0000000..5e43409 --- /dev/null +++ b/example_code/item_75.py @@ -0,0 +1,123 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +print('foo bar') + + +# Example 2 +my_value = 'foo bar' +print(str(my_value)) +print('%s' % my_value) +print(f'{my_value}') +print(format(my_value)) +print(my_value.__format__('s')) +print(my_value.__str__()) + + +# Example 3 +print(5) +print('5') + +int_value = 5 +str_value = '5' +print(f'{int_value} == {str_value} ?') + + +# Example 4 +a = '\x07' +print(repr(a)) + + +# Example 5 +b = eval(repr(a)) +assert a == b + + +# Example 6 +print(repr(5)) +print(repr('5')) + + +# Example 7 +print('%r' % 5) +print('%r' % '5') + +int_value = 5 +str_value = '5' +print(f'{int_value!r} != {str_value!r}') + + +# Example 8 +class OpaqueClass: + def __init__(self, x, y): + self.x = x + self.y = y + +obj = OpaqueClass(1, 'foo') +print(obj) + + +# Example 9 +class BetterClass: + def __init__(self, x, y): + self.x = x + self.y = y + + def __repr__(self): + return f'BetterClass({self.x!r}, {self.y!r})' + + +# Example 10 +obj = BetterClass(2, 'bar') +print(obj) + + +# Example 11 +obj = OpaqueClass(4, 'baz') +print(obj.__dict__) diff --git a/example_code/item_76/testing/assert_test.py b/example_code/item_76/testing/assert_test.py new file mode 100755 index 0000000..ebf9bb5 --- /dev/null +++ b/example_code/item_76/testing/assert_test.py @@ -0,0 +1,32 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase, main +from utils import to_str + +class AssertTestCase(TestCase): + def test_assert_helper(self): + expected = 12 + found = 2 * 5 + self.assertEqual(expected, found) + + def test_assert_statement(self): + expected = 12 + found = 2 * 5 + assert expected == found + +if __name__ == '__main__': + main() diff --git a/example_code/item_76/testing/data_driven_test.py b/example_code/item_76/testing/data_driven_test.py new file mode 100755 index 0000000..8255196 --- /dev/null +++ b/example_code/item_76/testing/data_driven_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase, main +from utils import to_str + +class DataDrivenTestCase(TestCase): + def test_good(self): + good_cases = [ + (b'my bytes', 'my bytes'), + ('no error', b'no error'), # This one will fail + ('other str', 'other str'), + ] + for value, expected in good_cases: + with self.subTest(value): + self.assertEqual(expected, to_str(value)) + + def test_bad(self): + bad_cases = [ + (object(), TypeError), + (b'\xfa\xfa', UnicodeDecodeError), + ] + for value, exception in bad_cases: + with self.subTest(value): + with self.assertRaises(exception): + to_str(value) + +if __name__ == '__main__': + main() diff --git a/example_code/item_76/testing/helper_test.py b/example_code/item_76/testing/helper_test.py new file mode 100755 index 0000000..6796b2a --- /dev/null +++ b/example_code/item_76/testing/helper_test.py @@ -0,0 +1,69 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase, main + +def sum_squares(values): + cumulative = 0 + for value in values: + cumulative += value ** 2 + yield cumulative + +class HelperTestCase(TestCase): + def verify_complex_case(self, values, expected): + expect_it = iter(expected) + found_it = iter(sum_squares(values)) + test_it = zip(expect_it, found_it) + + for i, (expect, found) in enumerate(test_it): + self.assertEqual( + expect, + found, + f'Index {i} is wrong') + + # Verify both generators are exhausted + try: + next(expect_it) + except StopIteration: + pass + else: + self.fail('Expected longer than found') + + try: + next(found_it) + except StopIteration: + pass + else: + self.fail('Found longer than expected') + + def test_wrong_lengths(self): + values = [1.1, 2.2, 3.3] + expected = [ + 1.1**2, + ] + self.verify_complex_case(values, expected) + + def test_wrong_results(self): + values = [1.1, 2.2, 3.3] + expected = [ + 1.1**2, + 1.1**2 + 2.2**2, + 1.1**2 + 2.2**2 + 3.3**2 + 4.4**2, + ] + self.verify_complex_case(values, expected) + +if __name__ == '__main__': + main() diff --git a/example_code/item_76/testing/utils.py b/example_code/item_76/testing/utils.py new file mode 100755 index 0000000..30b0787 --- /dev/null +++ b/example_code/item_76/testing/utils.py @@ -0,0 +1,24 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def to_str(data): + if isinstance(data, str): + return data + elif isinstance(data, bytes): + return data.decode('utf-8') + else: + raise TypeError('Must supply str or bytes, ' + 'found: %r' % data) diff --git a/example_code/item_76/testing/utils_error_test.py b/example_code/item_76/testing/utils_error_test.py new file mode 100755 index 0000000..36ccbbe --- /dev/null +++ b/example_code/item_76/testing/utils_error_test.py @@ -0,0 +1,30 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase, main +from utils import to_str + +class UtilsErrorTestCase(TestCase): + def test_to_str_bad(self): + with self.assertRaises(TypeError): + to_str(object()) + + def test_to_str_bad_encoding(self): + with self.assertRaises(UnicodeDecodeError): + to_str(b'\xfa\xfa') + +if __name__ == '__main__': + main() diff --git a/example_code/item_76/testing/utils_test.py b/example_code/item_76/testing/utils_test.py new file mode 100755 index 0000000..258dcd6 --- /dev/null +++ b/example_code/item_76/testing/utils_test.py @@ -0,0 +1,31 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase, main +from utils import to_str + +class UtilsTestCase(TestCase): + def test_to_str_bytes(self): + self.assertEqual('hello', to_str(b'hello')) + + def test_to_str_str(self): + self.assertEqual('hello', to_str('hello')) + + def test_failing(self): + self.assertEqual('incorrect', to_str('hello')) + +if __name__ == '__main__': + main() diff --git a/example_code/item_77/testing/environment_test.py b/example_code/item_77/testing/environment_test.py new file mode 100755 index 0000000..527f958 --- /dev/null +++ b/example_code/item_77/testing/environment_test.py @@ -0,0 +1,34 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest import TestCase, main + +class EnvironmentTest(TestCase): + def setUp(self): + self.test_dir = TemporaryDirectory() + self.test_path = Path(self.test_dir.name) + + def tearDown(self): + self.test_dir.cleanup() + + def test_modify_file(self): + with open(self.test_path / 'data.bin', 'w') as f: + f.write('hello') + +if __name__ == '__main__': + main() diff --git a/example_code/item_77/testing/integration_test.py b/example_code/item_77/testing/integration_test.py new file mode 100755 index 0000000..c722c87 --- /dev/null +++ b/example_code/item_77/testing/integration_test.py @@ -0,0 +1,39 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase, main + +def setUpModule(): + print('* Module setup') + +def tearDownModule(): + print('* Module clean-up') + +class IntegrationTest(TestCase): + def setUp(self): + print('* Test setup') + + def tearDown(self): + print('* Test clean-up') + + def test_end_to_end1(self): + print('* Test 1') + + def test_end_to_end2(self): + print('* Test 2') + +if __name__ == '__main__': + main() diff --git a/example_code/item_78.py b/example_code/item_78.py new file mode 100755 index 0000000..672ee1b --- /dev/null +++ b/example_code/item_78.py @@ -0,0 +1,307 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class DatabaseConnection: + def __init__(self, host, port): + pass + +class DatabaseConnectionError(Exception): + pass + +def get_animals(database, species): + # Query the Database + raise DatabaseConnectionError('Not connected') + # Return a list of (name, last_mealtime) tuples + + +# Example 2 +try: + database = DatabaseConnection('localhost', '4444') + + get_animals(database, 'Meerkat') +except: + logging.exception('Expected') +else: + assert False + + +# Example 3 +from datetime import datetime +from unittest.mock import Mock + +mock = Mock(spec=get_animals) +expected = [ + ('Spot', datetime(2019, 6, 5, 11, 15)), + ('Fluffy', datetime(2019, 6, 5, 12, 30)), + ('Jojo', datetime(2019, 6, 5, 12, 45)), +] +mock.return_value = expected + + +# Example 4 +try: + mock.does_not_exist +except: + logging.exception('Expected') +else: + assert False + + +# Example 5 +database = object() +result = mock(database, 'Meerkat') +assert result == expected + + +# Example 6 +mock.assert_called_once_with(database, 'Meerkat') + + +# Example 7 +try: + mock.assert_called_once_with(database, 'Giraffe') +except: + logging.exception('Expected') +else: + assert False + + +# Example 8 +from unittest.mock import ANY + +mock = Mock(spec=get_animals) +mock('database 1', 'Rabbit') +mock('database 2', 'Bison') +mock('database 3', 'Meerkat') + +mock.assert_called_with(ANY, 'Meerkat') + + +# Example 9 +try: + class MyError(Exception): + pass + + mock = Mock(spec=get_animals) + mock.side_effect = MyError('Whoops! Big problem') + result = mock(database, 'Meerkat') +except: + logging.exception('Expected') +else: + assert False + + +# Example 10 +def get_food_period(database, species): + # Query the Database + pass + # Return a time delta + +def feed_animal(database, name, when): + # Write to the Database + pass + +def do_rounds(database, species): + now = datetime.datetime.utcnow() + feeding_timedelta = get_food_period(database, species) + animals = get_animals(database, species) + fed = 0 + + for name, last_mealtime in animals: + if (now - last_mealtime) > feeding_timedelta: + feed_animal(database, name, now) + fed += 1 + + return fed + + +# Example 11 +def do_rounds(database, species, *, + now_func=datetime.utcnow, + food_func=get_food_period, + animals_func=get_animals, + feed_func=feed_animal): + now = now_func() + feeding_timedelta = food_func(database, species) + animals = animals_func(database, species) + fed = 0 + + for name, last_mealtime in animals: + if (now - last_mealtime) > feeding_timedelta: + feed_func(database, name, now) + fed += 1 + + return fed + + +# Example 12 +from datetime import timedelta + +now_func = Mock(spec=datetime.utcnow) +now_func.return_value = datetime(2019, 6, 5, 15, 45) + +food_func = Mock(spec=get_food_period) +food_func.return_value = timedelta(hours=3) + +animals_func = Mock(spec=get_animals) +animals_func.return_value = [ + ('Spot', datetime(2019, 6, 5, 11, 15)), + ('Fluffy', datetime(2019, 6, 5, 12, 30)), + ('Jojo', datetime(2019, 6, 5, 12, 45)), +] + +feed_func = Mock(spec=feed_animal) + + +# Example 13 +result = do_rounds( + database, + 'Meerkat', + now_func=now_func, + food_func=food_func, + animals_func=animals_func, + feed_func=feed_func) + +assert result == 2 + + +# Example 14 +from unittest.mock import call + +food_func.assert_called_once_with(database, 'Meerkat') + +animals_func.assert_called_once_with(database, 'Meerkat') + +feed_func.assert_has_calls( + [ + call(database, 'Spot', now_func.return_value), + call(database, 'Fluffy', now_func.return_value), + ], + any_order=True) + + +# Example 15 +from unittest.mock import patch + +print('Outside patch:', get_animals) + +with patch('__main__.get_animals'): + print('Inside patch: ', get_animals) + +print('Outside again:', get_animals) + + +# Example 16 +try: + fake_now = datetime(2019, 6, 5, 15, 45) + + with patch('datetime.datetime.utcnow'): + datetime.utcnow.return_value = fake_now +except: + logging.exception('Expected') +else: + assert False + + +# Example 17 +def get_do_rounds_time(): + return datetime.datetime.utcnow() + +def do_rounds(database, species): + now = get_do_rounds_time() + +with patch('__main__.get_do_rounds_time'): + pass + + +# Example 18 +def do_rounds(database, species, *, utcnow=datetime.utcnow): + now = utcnow() + feeding_timedelta = get_food_period(database, species) + animals = get_animals(database, species) + fed = 0 + + for name, last_mealtime in animals: + if (now - last_mealtime) > feeding_timedelta: + feed_func(database, name, now) + fed += 1 + + return fed + + +# Example 19 +from unittest.mock import DEFAULT + +with patch.multiple('__main__', + autospec=True, + get_food_period=DEFAULT, + get_animals=DEFAULT, + feed_animal=DEFAULT): + now_func = Mock(spec=datetime.utcnow) + now_func.return_value = datetime(2019, 6, 5, 15, 45) + get_food_period.return_value = timedelta(hours=3) + get_animals.return_value = [ + ('Spot', datetime(2019, 6, 5, 11, 15)), + ('Fluffy', datetime(2019, 6, 5, 12, 30)), + ('Jojo', datetime(2019, 6, 5, 12, 45)) + ] + + +# Example 20 + result = do_rounds(database, 'Meerkat', utcnow=now_func) + assert result == 2 + + food_func.assert_called_once_with(database, 'Meerkat') + animals_func.assert_called_once_with(database, 'Meerkat') + feed_func.assert_has_calls( + [ + call(database, 'Spot', now_func.return_value), + call(database, 'Fluffy', now_func.return_value), + ], + any_order=True) diff --git a/example_code/item_79.py b/example_code/item_79.py new file mode 100755 index 0000000..1769e1a --- /dev/null +++ b/example_code/item_79.py @@ -0,0 +1,166 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +class ZooDatabase: + + def get_animals(self, species): + pass + + def get_food_period(self, species): + pass + + def feed_animal(self, name, when): + pass + + +# Example 2 +from datetime import datetime + +def do_rounds(database, species, *, utcnow=datetime.utcnow): + now = utcnow() + feeding_timedelta = database.get_food_period(species) + animals = database.get_animals(species) + fed = 0 + + for name, last_mealtime in animals: + if (now - last_mealtime) >= feeding_timedelta: + database.feed_animal(name, now) + fed += 1 + + return fed + + +# Example 3 +from unittest.mock import Mock + +database = Mock(spec=ZooDatabase) +print(database.feed_animal) +database.feed_animal() +database.feed_animal.assert_any_call() + + +# Example 4 +from datetime import timedelta +from unittest.mock import call + +now_func = Mock(spec=datetime.utcnow) +now_func.return_value = datetime(2019, 6, 5, 15, 45) + +database = Mock(spec=ZooDatabase) +database.get_food_period.return_value = timedelta(hours=3) +database.get_animals.return_value = [ + ('Spot', datetime(2019, 6, 5, 11, 15)), + ('Fluffy', datetime(2019, 6, 5, 12, 30)), + ('Jojo', datetime(2019, 6, 5, 12, 55)) +] + + +# Example 5 +result = do_rounds(database, 'Meerkat', utcnow=now_func) +assert result == 2 + +database.get_food_period.assert_called_once_with('Meerkat') +database.get_animals.assert_called_once_with('Meerkat') +database.feed_animal.assert_has_calls( + [ + call('Spot', now_func.return_value), + call('Fluffy', now_func.return_value), + ], + any_order=True) + + +# Example 6 +try: + database.bad_method_name() +except: + logging.exception('Expected') +else: + assert False + + +# Example 7 +DATABASE = None + +def get_database(): + global DATABASE + if DATABASE is None: + DATABASE = ZooDatabase() + return DATABASE + +def main(argv): + database = get_database() + species = argv[1] + count = do_rounds(database, species) + print(f'Fed {count} {species}(s)') + return 0 + + +# Example 8 +import contextlib +import io +from unittest.mock import patch + +with patch('__main__.DATABASE', spec=ZooDatabase): + now = datetime.utcnow() + + DATABASE.get_food_period.return_value = timedelta(hours=3) + DATABASE.get_animals.return_value = [ + ('Spot', now - timedelta(minutes=4.5)), + ('Fluffy', now - timedelta(hours=3.25)), + ('Jojo', now - timedelta(hours=3)), + ] + + fake_stdout = io.StringIO() + with contextlib.redirect_stdout(fake_stdout): + main(['program name', 'Meerkat']) + + found = fake_stdout.getvalue() + expected = 'Fed 2 Meerkat(s)\n' + + assert found == expected diff --git a/example_code/item_80/debugging/always_breakpoint.py b/example_code/item_80/debugging/always_breakpoint.py new file mode 100755 index 0000000..582a050 --- /dev/null +++ b/example_code/item_80/debugging/always_breakpoint.py @@ -0,0 +1,35 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math + +def compute_rmse(observed, ideal): + total_err_2 = 0 + count = 0 + for got, wanted in zip(observed, ideal): + err_2 = (got - wanted) ** 2 + breakpoint() # Start the debugger here + total_err_2 += err_2 + count += 1 + + mean_err = total_err_2 / count + rmse = math.sqrt(mean_err) + return rmse + +result = compute_rmse( + [1.8, 1.7, 3.2, 6], + [2, 1.5, 3, 5]) +print(result) diff --git a/example_code/item_80/debugging/conditional_breakpoint.py b/example_code/item_80/debugging/conditional_breakpoint.py new file mode 100755 index 0000000..6540c3f --- /dev/null +++ b/example_code/item_80/debugging/conditional_breakpoint.py @@ -0,0 +1,34 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +def compute_rmse(observed, ideal): + total_err_2 = 0 + count = 0 + for got, wanted in zip(observed, ideal): + err_2 = (got - wanted) ** 2 + if err_2 >= 1: # Start the debugger if True + breakpoint() + total_err_2 += err_2 + count += 1 + mean_err = total_err_2 / count + rmse = math.sqrt(mean_err) + return rmse + +result = compute_rmse( + [1.8, 1.7, 3.2, 7], + [2, 1.5, 3, 5]) +print(result) diff --git a/example_code/item_80/debugging/my_module.py b/example_code/item_80/debugging/my_module.py new file mode 100755 index 0000000..e22ca05 --- /dev/null +++ b/example_code/item_80/debugging/my_module.py @@ -0,0 +1,31 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math + +def squared_error(point, mean): + err = point - mean + return err ** 2 + +def compute_variance(data): + mean = sum(data) / len(data) + err_2_sum = sum(squared_error(x, mean) for x in data) + variance = err_2_sum / (len(data) - 1) + return variance + +def compute_stddev(data): + variance = compute_variance(data) + return math.sqrt(variance) diff --git a/example_code/item_80/debugging/postmortem_breakpoint.py b/example_code/item_80/debugging/postmortem_breakpoint.py new file mode 100755 index 0000000..9a6e365 --- /dev/null +++ b/example_code/item_80/debugging/postmortem_breakpoint.py @@ -0,0 +1,34 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math + +def compute_rmse(observed, ideal): + total_err_2 = 0 + count = 0 + for got, wanted in zip(observed, ideal): + err_2 = (got - wanted) ** 2 + total_err_2 += err_2 + count += 1 + + mean_err = total_err_2 / count + rmse = math.sqrt(mean_err) + return rmse + +result = compute_rmse( + [1.8, 1.7, 3.2, 7j], # Bad input + [2, 1.5, 3, 5]) +print(result) diff --git a/example_code/item_81/tracemalloc/top_n.py b/example_code/item_81/tracemalloc/top_n.py new file mode 100755 index 0000000..8e7e698 --- /dev/null +++ b/example_code/item_81/tracemalloc/top_n.py @@ -0,0 +1,29 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import tracemalloc + +tracemalloc.start(10) # Set stack depth +time1 = tracemalloc.take_snapshot() # Before snapshot + +import waste_memory + +x = waste_memory.run() # Usage to debug +time2 = tracemalloc.take_snapshot() # After snapshot + +stats = time2.compare_to(time1, 'lineno') # Compare snapshots +for stat in stats[:3]: + print(stat) diff --git a/example_code/item_81/tracemalloc/using_gc.py b/example_code/item_81/tracemalloc/using_gc.py new file mode 100755 index 0000000..1fe0574 --- /dev/null +++ b/example_code/item_81/tracemalloc/using_gc.py @@ -0,0 +1,31 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import gc + +found_objects = gc.get_objects() +print('Before:', len(found_objects)) + +import waste_memory + +hold_reference = waste_memory.run() + +found_objects = gc.get_objects() +print('After: ', len(found_objects)) +for obj in found_objects[:3]: + print(repr(obj)[:100]) + +print('...') diff --git a/example_code/item_81/tracemalloc/waste_memory.py b/example_code/item_81/tracemalloc/waste_memory.py new file mode 100755 index 0000000..5afd71b --- /dev/null +++ b/example_code/item_81/tracemalloc/waste_memory.py @@ -0,0 +1,35 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# waste_memory.py +import os + +class MyObject: + def __init__(self): + self.data = os.urandom(100) + +def get_data(): + values = [] + for _ in range(100): + obj = MyObject() + values.append(obj) + return values + +def run(): + deep_values = [] + for _ in range(100): + deep_values.append(get_data()) + return deep_values diff --git a/example_code/item_81/tracemalloc/with_trace.py b/example_code/item_81/tracemalloc/with_trace.py new file mode 100755 index 0000000..4f506ee --- /dev/null +++ b/example_code/item_81/tracemalloc/with_trace.py @@ -0,0 +1,30 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import tracemalloc + +tracemalloc.start(10) +time1 = tracemalloc.take_snapshot() + +import waste_memory + +x = waste_memory.run() +time2 = tracemalloc.take_snapshot() + +stats = time2.compare_to(time1, 'traceback') +top = stats[0] +print('Biggest offender is:') +print('\n'.join(top.traceback.format())) diff --git a/example_code/item_84.py b/example_code/item_84.py new file mode 100755 index 0000000..f673237 --- /dev/null +++ b/example_code/item_84.py @@ -0,0 +1,112 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +def palindrome(word): + """Return True if the given word is a palindrome.""" + return word == word[::-1] + +assert palindrome('tacocat') +assert not palindrome('banana') + + +# Example 2 +print(repr(palindrome.__doc__)) + + +# Example 3 +"""Library for finding linguistic patterns in words. + +Testing how words relate to each other can be tricky sometimes! +This module provides easy ways to determine when words you've +found have special properties. + +Available functions: +- palindrome: Determine if a word is a palindrome. +- check_anagram: Determine if two words are anagrams. +... +""" + + +# Example 4 +class Player: + """Represents a player of the game. + + Subclasses may override the 'tick' method to provide + custom animations for the player's movement depending + on their power level, etc. + + Public attributes: + - power: Unused power-ups (float between 0 and 1). + - coins: Coins found during the level (integer). + """ + + +# Example 5 +import itertools +def find_anagrams(word, dictionary): + """Find all anagrams for a word. + + This function only runs as fast as the test for + membership in the 'dictionary' container. + + Args: + word: String of the target word. + dictionary: collections.abc.Container with all + strings that are known to be actual words. + + Returns: + List of anagrams that were found. Empty if + none were found. + """ + permutations = itertools.permutations(word, len(word)) + possible = (''.join(x) for x in permutations) + found = {word for word in possible if word in dictionary} + return list(found) + +assert find_anagrams('pancakes', ['scanpeak']) == ['scanpeak'] diff --git a/example_code/item_84_example_06.py b/example_code/item_84_example_06.py new file mode 100755 index 0000000..99a4915 --- /dev/null +++ b/example_code/item_84_example_06.py @@ -0,0 +1,26 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +# Example 6 +# Check types in this file with: python -m mypy + +from typing import Container, List + +def find_anagrams(word: str, + dictionary: Container[str]) -> List[str]: + pass diff --git a/example_code/item_84_example_07.py b/example_code/item_84_example_07.py new file mode 100755 index 0000000..e6d4bc2 --- /dev/null +++ b/example_code/item_84_example_07.py @@ -0,0 +1,38 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +# Example 7 +# Check types in this file with: python -m mypy + +from typing import Container, List + +def find_anagrams(word: str, + dictionary: Container[str]) -> List[str]: + """Find all anagrams for a word. + + This function only runs as fast as the test for + membership in the 'dictionary' container. + + Args: + word: Target word. + dictionary: All known actual words. + + Returns: + Anagrams that were found. + """ + pass diff --git a/example_code/item_85/api_package/api_consumer.py b/example_code/item_85/api_package/api_consumer.py new file mode 100755 index 0000000..46092f7 --- /dev/null +++ b/example_code/item_85/api_package/api_consumer.py @@ -0,0 +1,31 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from mypackage import * + +a = Projectile(1.5, 3) +b = Projectile(4, 1.7) +after_a, after_b = simulate_collision(a, b) +print(after_a.__dict__, after_b.__dict__) + +import mypackage +try: + mypackage._dot_product + assert False +except AttributeError: + pass # Expected + +mypackage.utils._dot_product # But this is defined diff --git a/example_code/item_85/api_package/main.py b/example_code/item_85/api_package/main.py new file mode 100755 index 0000000..636228d --- /dev/null +++ b/example_code/item_85/api_package/main.py @@ -0,0 +1,17 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from mypackage import utils diff --git a/example_code/item_85/api_package/mypackage/__init__.py b/example_code/item_85/api_package/mypackage/__init__.py new file mode 100755 index 0000000..025222d --- /dev/null +++ b/example_code/item_85/api_package/mypackage/__init__.py @@ -0,0 +1,21 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__all__ = [] +from . models import * +__all__ += models.__all__ +from . utils import * +__all__ += utils.__all__ diff --git a/example_code/item_85/api_package/mypackage/models.py b/example_code/item_85/api_package/mypackage/models.py new file mode 100755 index 0000000..b72c3c6 --- /dev/null +++ b/example_code/item_85/api_package/mypackage/models.py @@ -0,0 +1,22 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__all__ = ['Projectile'] + +class Projectile: + def __init__(self, mass, velocity): + self.mass = mass + self.velocity = velocity diff --git a/example_code/item_85/api_package/mypackage/utils.py b/example_code/item_85/api_package/mypackage/utils.py new file mode 100755 index 0000000..fc37afa --- /dev/null +++ b/example_code/item_85/api_package/mypackage/utils.py @@ -0,0 +1,27 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . models import Projectile + +__all__ = ['simulate_collision'] + +def _dot_product(a, b): + pass + +def simulate_collision(a, b): + after_a = Projectile(-a.mass, -a.velocity) + after_b = Projectile(-b.mass, -b.velocity) + return after_a, after_b diff --git a/example_code/item_85/namespace_package/analysis/__init__.py b/example_code/item_85/namespace_package/analysis/__init__.py new file mode 100755 index 0000000..18e252f --- /dev/null +++ b/example_code/item_85/namespace_package/analysis/__init__.py @@ -0,0 +1,17 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + diff --git a/example_code/item_85/namespace_package/analysis/utils.py b/example_code/item_85/namespace_package/analysis/utils.py new file mode 100755 index 0000000..972a3fb --- /dev/null +++ b/example_code/item_85/namespace_package/analysis/utils.py @@ -0,0 +1,22 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +def log_base2_bucket(value): + return math.log(value, 2) + +def inspect(value): + pass diff --git a/example_code/item_85/namespace_package/frontend/__init__.py b/example_code/item_85/namespace_package/frontend/__init__.py new file mode 100755 index 0000000..18e252f --- /dev/null +++ b/example_code/item_85/namespace_package/frontend/__init__.py @@ -0,0 +1,17 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + diff --git a/example_code/item_85/namespace_package/frontend/utils.py b/example_code/item_85/namespace_package/frontend/utils.py new file mode 100755 index 0000000..69ed8a7 --- /dev/null +++ b/example_code/item_85/namespace_package/frontend/utils.py @@ -0,0 +1,21 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def stringify(value): + return str(value) + +def inspect(value): + pass diff --git a/example_code/item_85/namespace_package/main.py b/example_code/item_85/namespace_package/main.py new file mode 100755 index 0000000..4bbf563 --- /dev/null +++ b/example_code/item_85/namespace_package/main.py @@ -0,0 +1,21 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from analysis.utils import log_base2_bucket +from frontend.utils import stringify + +bucket = stringify(log_base2_bucket(33)) +print(repr(bucket)) diff --git a/example_code/item_85/namespace_package/main2.py b/example_code/item_85/namespace_package/main2.py new file mode 100755 index 0000000..8b4bb85 --- /dev/null +++ b/example_code/item_85/namespace_package/main2.py @@ -0,0 +1,20 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from analysis.utils import inspect +from frontend.utils import inspect # Overwrites! +'frontend' in inspect.__module__ +print(inspect.__module__) diff --git a/example_code/item_85/namespace_package/main3.py b/example_code/item_85/namespace_package/main3.py new file mode 100755 index 0000000..e013b73 --- /dev/null +++ b/example_code/item_85/namespace_package/main3.py @@ -0,0 +1,22 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from analysis.utils import inspect as analysis_inspect +from frontend.utils import inspect as frontend_inspect + +value = 33 +if analysis_inspect(value) == frontend_inspect(value): + print('Inspection equal!') diff --git a/example_code/item_85/namespace_package/main4.py b/example_code/item_85/namespace_package/main4.py new file mode 100755 index 0000000..0dfb064 --- /dev/null +++ b/example_code/item_85/namespace_package/main4.py @@ -0,0 +1,23 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import analysis.utils +import frontend.utils + +value = 33 +if (analysis.utils.inspect(value) == + frontend.utils.inspect(value)): + print('Inspection equal!') diff --git a/example_code/item_86.py b/example_code/item_86.py new file mode 100755 index 0000000..492fa14 --- /dev/null +++ b/example_code/item_86.py @@ -0,0 +1,62 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 4 +# db_connection.py +import sys + +class Win32Database: + pass + +class PosixDatabase: + pass + +if sys.platform.startswith('win32'): + Database = Win32Database +else: + Database = PosixDatabase diff --git a/example_code/item_86/module_scope/db_connection.py b/example_code/item_86/module_scope/db_connection.py new file mode 100755 index 0000000..df269db --- /dev/null +++ b/example_code/item_86/module_scope/db_connection.py @@ -0,0 +1,29 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# db_connection.py +import __main__ + +class TestingDatabase: + pass + +class RealDatabase: + pass + +if __main__.TESTING: + Database = TestingDatabase +else: + Database = RealDatabase diff --git a/example_code/item_86/module_scope/dev_main.py b/example_code/item_86/module_scope/dev_main.py new file mode 100755 index 0000000..cfcb4c5 --- /dev/null +++ b/example_code/item_86/module_scope/dev_main.py @@ -0,0 +1,21 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +TESTING = True + +import db_connection + +db = db_connection.Database() diff --git a/example_code/item_86/module_scope/prod_main.py b/example_code/item_86/module_scope/prod_main.py new file mode 100755 index 0000000..3dda7da --- /dev/null +++ b/example_code/item_86/module_scope/prod_main.py @@ -0,0 +1,21 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +TESTING = False + +import db_connection + +db = db_connection.Database() diff --git a/example_code/item_87.py b/example_code/item_87.py new file mode 100755 index 0000000..07c20c9 --- /dev/null +++ b/example_code/item_87.py @@ -0,0 +1,189 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +# my_module.py +def determine_weight(volume, density): + if density <= 0: + raise ValueError('Density must be positive') + +try: + determine_weight(1, 0) +except ValueError: + pass +else: + assert False + + +# Example 2 +# my_module.py +class Error(Exception): + """Base-class for all exceptions raised by this module.""" + +class InvalidDensityError(Error): + """There was a problem with a provided density value.""" + +class InvalidVolumeError(Error): + """There was a problem with the provided weight value.""" + +def determine_weight(volume, density): + if density < 0: + raise InvalidDensityError('Density must be positive') + if volume < 0: + raise InvalidVolumeError('Volume must be positive') + if volume == 0: + density / volume + + +# Example 3 +class my_module: + Error = Error + InvalidDensityError = InvalidDensityError + + @staticmethod + def determine_weight(volume, density): + if density < 0: + raise InvalidDensityError('Density must be positive') + if volume < 0: + raise InvalidVolumeError('Volume must be positive') + if volume == 0: + density / volume + +try: + weight = my_module.determine_weight(1, -1) +except my_module.Error: + logging.exception('Unexpected error') +else: + assert False + + +# Example 4 +SENTINEL = object() +weight = SENTINEL +try: + weight = my_module.determine_weight(-1, 1) +except my_module.InvalidDensityError: + weight = 0 +except my_module.Error: + logging.exception('Bug in the calling code') +else: + assert False + +assert weight is SENTINEL + + +# Example 5 +try: + weight = SENTINEL + try: + weight = my_module.determine_weight(0, 1) + except my_module.InvalidDensityError: + weight = 0 + except my_module.Error: + logging.exception('Bug in the calling code') + except Exception: + logging.exception('Bug in the API code!') + raise # Re-raise exception to the caller + else: + assert False + + assert weight == 0 +except: + logging.exception('Expected') +else: + assert False + + +# Example 6 +# my_module.py + +class NegativeDensityError(InvalidDensityError): + """A provided density value was negative.""" + + +def determine_weight(volume, density): + if density < 0: + raise NegativeDensityError('Density must be positive') + + +# Example 7 +try: + my_module.NegativeDensityError = NegativeDensityError + my_module.determine_weight = determine_weight + try: + weight = my_module.determine_weight(1, -1) + except my_module.NegativeDensityError: + raise ValueError('Must supply non-negative density') + except my_module.InvalidDensityError: + weight = 0 + except my_module.Error: + logging.exception('Bug in the calling code') + except Exception: + logging.exception('Bug in the API code!') + raise + else: + assert False +except: + logging.exception('Expected') +else: + assert False + + +# Example 8 +# my_module.py +class Error(Exception): + """Base-class for all exceptions raised by this module.""" + +class WeightError(Error): + """Base-class for weight calculation errors.""" + +class VolumeError(Error): + """Base-class for volume calculation errors.""" + +class DensityError(Error): + """Base-class for density calculation errors.""" diff --git a/example_code/item_88/recursive_import_bad/app.py b/example_code/item_88/recursive_import_bad/app.py new file mode 100755 index 0000000..cac1ea8 --- /dev/null +++ b/example_code/item_88/recursive_import_bad/app.py @@ -0,0 +1,24 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import dialog + +class Prefs: + def get(self, name): + pass + +prefs = Prefs() +dialog.show() diff --git a/example_code/item_88/recursive_import_bad/dialog.py b/example_code/item_88/recursive_import_bad/dialog.py new file mode 100755 index 0000000..44aa232 --- /dev/null +++ b/example_code/item_88/recursive_import_bad/dialog.py @@ -0,0 +1,26 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import app + +class Dialog: + def __init__(self, save_dir): + self.save_dir = save_dir + +save_dialog = Dialog(app.prefs.get('save_dir')) + +def show(): + print('Showing the dialog!') diff --git a/example_code/item_88/recursive_import_bad/main.py b/example_code/item_88/recursive_import_bad/main.py new file mode 100755 index 0000000..eeeba96 --- /dev/null +++ b/example_code/item_88/recursive_import_bad/main.py @@ -0,0 +1,17 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import app diff --git a/example_code/item_88/recursive_import_dynamic/app.py b/example_code/item_88/recursive_import_dynamic/app.py new file mode 100755 index 0000000..cac1ea8 --- /dev/null +++ b/example_code/item_88/recursive_import_dynamic/app.py @@ -0,0 +1,24 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import dialog + +class Prefs: + def get(self, name): + pass + +prefs = Prefs() +dialog.show() diff --git a/example_code/item_88/recursive_import_dynamic/dialog.py b/example_code/item_88/recursive_import_dynamic/dialog.py new file mode 100755 index 0000000..de500cf --- /dev/null +++ b/example_code/item_88/recursive_import_dynamic/dialog.py @@ -0,0 +1,31 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reenabling this will break things. +# import app + +class Dialog: + def __init__(self): + pass + +# Using this instead will break things +# save_dialog = Dialog(app.prefs.get('save_dir')) +save_dialog = Dialog() + +def show(): + import app # Dynamic import + save_dialog.save_dir = app.prefs.get('save_dir') + print('Showing the dialog!') diff --git a/example_code/item_88/recursive_import_dynamic/main.py b/example_code/item_88/recursive_import_dynamic/main.py new file mode 100755 index 0000000..eeeba96 --- /dev/null +++ b/example_code/item_88/recursive_import_dynamic/main.py @@ -0,0 +1,17 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import app diff --git a/example_code/item_88/recursive_import_nosideeffects/app.py b/example_code/item_88/recursive_import_nosideeffects/app.py new file mode 100755 index 0000000..7cd5bc0 --- /dev/null +++ b/example_code/item_88/recursive_import_nosideeffects/app.py @@ -0,0 +1,26 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import dialog + +class Prefs: + def get(self, name): + pass + +prefs = Prefs() + +def configure(): + pass diff --git a/example_code/item_88/recursive_import_nosideeffects/dialog.py b/example_code/item_88/recursive_import_nosideeffects/dialog.py new file mode 100755 index 0000000..9c5b772 --- /dev/null +++ b/example_code/item_88/recursive_import_nosideeffects/dialog.py @@ -0,0 +1,29 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import app + +class Dialog: + def __init__(self): + pass + +save_dialog = Dialog() + +def show(): + print('Showing the dialog!') + +def configure(): + save_dialog.save_dir = app.prefs.get('save_dir') diff --git a/example_code/item_88/recursive_import_nosideeffects/main.py b/example_code/item_88/recursive_import_nosideeffects/main.py new file mode 100755 index 0000000..4bcbbff --- /dev/null +++ b/example_code/item_88/recursive_import_nosideeffects/main.py @@ -0,0 +1,23 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import app +import dialog + +app.configure() +dialog.configure() + +dialog.show() diff --git a/example_code/item_88/recursive_import_ordering/app.py b/example_code/item_88/recursive_import_ordering/app.py new file mode 100755 index 0000000..c5abf30 --- /dev/null +++ b/example_code/item_88/recursive_import_ordering/app.py @@ -0,0 +1,24 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class Prefs: + def get(self, name): + pass + +prefs = Prefs() + +import dialog # Moved +dialog.show() diff --git a/example_code/item_88/recursive_import_ordering/dialog.py b/example_code/item_88/recursive_import_ordering/dialog.py new file mode 100755 index 0000000..44aa232 --- /dev/null +++ b/example_code/item_88/recursive_import_ordering/dialog.py @@ -0,0 +1,26 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import app + +class Dialog: + def __init__(self, save_dir): + self.save_dir = save_dir + +save_dialog = Dialog(app.prefs.get('save_dir')) + +def show(): + print('Showing the dialog!') diff --git a/example_code/item_88/recursive_import_ordering/main.py b/example_code/item_88/recursive_import_ordering/main.py new file mode 100755 index 0000000..eeeba96 --- /dev/null +++ b/example_code/item_88/recursive_import_ordering/main.py @@ -0,0 +1,17 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import app diff --git a/example_code/item_89.py b/example_code/item_89.py new file mode 100755 index 0000000..e9fe12a --- /dev/null +++ b/example_code/item_89.py @@ -0,0 +1,234 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +def print_distance(speed, duration): + distance = speed * duration + print(f'{distance} miles') + +print_distance(5, 2.5) + + +# Example 2 +print_distance(1000, 3) + + +# Example 3 +CONVERSIONS = { + 'mph': 1.60934 / 3600 * 1000, # m/s + 'hours': 3600, # seconds + 'miles': 1.60934 * 1000, # m + 'meters': 1, # m + 'm/s': 1, # m + 'seconds': 1, # s +} + +def convert(value, units): + rate = CONVERSIONS[units] + return rate * value + +def localize(value, units): + rate = CONVERSIONS[units] + return value / rate + +def print_distance(speed, duration, *, + speed_units='mph', + time_units='hours', + distance_units='miles'): + norm_speed = convert(speed, speed_units) + norm_duration = convert(duration, time_units) + norm_distance = norm_speed * norm_duration + distance = localize(norm_distance, distance_units) + print(f'{distance} {distance_units}') + + +# Example 4 +print_distance(1000, 3, + speed_units='meters', + time_units='seconds') + + +# Example 5 +import warnings + +def print_distance(speed, duration, *, + speed_units=None, + time_units=None, + distance_units=None): + if speed_units is None: + warnings.warn( + 'speed_units required', DeprecationWarning) + speed_units = 'mph' + + if time_units is None: + warnings.warn( + 'time_units required', DeprecationWarning) + time_units = 'hours' + + if distance_units is None: + warnings.warn( + 'distance_units required', DeprecationWarning) + distance_units = 'miles' + + norm_speed = convert(speed, speed_units) + norm_duration = convert(duration, time_units) + norm_distance = norm_speed * norm_duration + distance = localize(norm_distance, distance_units) + print(f'{distance} {distance_units}') + + +# Example 6 +import contextlib +import io + +fake_stderr = io.StringIO() +with contextlib.redirect_stderr(fake_stderr): + print_distance(1000, 3, + speed_units='meters', + time_units='seconds') + +print(fake_stderr.getvalue()) + + +# Example 7 +def require(name, value, default): + if value is not None: + return value + warnings.warn( + f'{name} will be required soon, update your code', + DeprecationWarning, + stacklevel=3) + return default + +def print_distance(speed, duration, *, + speed_units=None, + time_units=None, + distance_units=None): + speed_units = require('speed_units', speed_units, 'mph') + time_units = require('time_units', time_units, 'hours') + distance_units = require( + 'distance_units', distance_units, 'miles') + + norm_speed = convert(speed, speed_units) + norm_duration = convert(duration, time_units) + norm_distance = norm_speed * norm_duration + distance = localize(norm_distance, distance_units) + print(f'{distance} {distance_units}') + + +# Example 8 +import contextlib +import io + +fake_stderr = io.StringIO() +with contextlib.redirect_stderr(fake_stderr): + print_distance(1000, 3, + speed_units='meters', + time_units='seconds') + +print(fake_stderr.getvalue()) + + +# Example 9 +warnings.simplefilter('error') +try: + warnings.warn('This usage is deprecated', + DeprecationWarning) +except DeprecationWarning: + pass # Expected +else: + assert False + +warnings.resetwarnings() + + +# Example 10 +warnings.resetwarnings() + +warnings.simplefilter('ignore') +warnings.warn('This will not be printed to stderr') + +warnings.resetwarnings() + + +# Example 11 +import logging + +fake_stderr = io.StringIO() +handler = logging.StreamHandler(fake_stderr) +formatter = logging.Formatter( + '%(asctime)-15s WARNING] %(message)s') +handler.setFormatter(formatter) + +logging.captureWarnings(True) +logger = logging.getLogger('py.warnings') +logger.addHandler(handler) +logger.setLevel(logging.DEBUG) + +warnings.resetwarnings() +warnings.simplefilter('default') +warnings.warn('This will go to the logs output') + +print(fake_stderr.getvalue()) + +warnings.resetwarnings() + + +# Example 12 +with warnings.catch_warnings(record=True) as found_warnings: + found = require('my_arg', None, 'fake units') + expected = 'fake units' + assert found == expected + + +# Example 13 +assert len(found_warnings) == 1 +single_warning = found_warnings[0] +assert str(single_warning.message) == ( + 'my_arg will be required soon, update your code') +assert single_warning.category == DeprecationWarning diff --git a/example_code/item_90.py b/example_code/item_90.py new file mode 100755 index 0000000..792a8c7 --- /dev/null +++ b/example_code/item_90.py @@ -0,0 +1,191 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reproduce book environment +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) + + +# Example 1 +try: + def subtract(a, b): + return a - b + + subtract(10, '5') +except: + logging.exception('Expected') +else: + assert False + + +# Example 3 +try: + def concat(a, b): + return a + b + + concat('first', b'second') +except: + logging.exception('Expected') +else: + assert False + + +# Example 5 +class Counter: + def __init__(self): + self.value = 0 + + def add(self, offset): + value += offset + + def get(self) -> int: + self.value + + +# Example 6 +try: + counter = Counter() + counter.add(5) +except: + logging.exception('Expected') +else: + assert False + + +# Example 7 +try: + counter = Counter() + found = counter.get() + assert found == 0, found +except: + logging.exception('Expected') +else: + assert False + + +# Example 9 +try: + def combine(func, values): + assert len(values) > 0 + + result = values[0] + for next_value in values[1:]: + result = func(result, next_value) + + return result + + def add(x, y): + return x + y + + inputs = [1, 2, 3, 4j] + result = combine(add, inputs) + assert result == 10, result # Fails +except: + logging.exception('Expected') +else: + assert False + + +# Example 11 +try: + def get_or_default(value, default): + if value is not None: + return value + return value + + found = get_or_default(3, 5) + assert found == 3 + + found = get_or_default(None, 5) + assert found == 5, found # Fails +except: + logging.exception('Expected') +else: + assert False + + +# Example 13 +class FirstClass: + def __init__(self, value): + self.value = value + +class SecondClass: + def __init__(self, value): + self.value = value + +second = SecondClass(5) +first = FirstClass(second) + +del FirstClass +del SecondClass + + +# Example 15 +try: + class FirstClass: + def __init__(self, value: SecondClass) -> None: # Breaks + self.value = value + + class SecondClass: + def __init__(self, value: int) -> None: + self.value = value + + second = SecondClass(5) + first = FirstClass(second) +except: + logging.exception('Expected') +else: + assert False + + +# Example 16 +class FirstClass: + def __init__(self, value: 'SecondClass') -> None: # OK + self.value = value + +class SecondClass: + def __init__(self, value: int) -> None: + self.value = value + +second = SecondClass(5) +first = FirstClass(second) diff --git a/example_code/item_90_example_02.py b/example_code/item_90_example_02.py new file mode 100755 index 0000000..012cffa --- /dev/null +++ b/example_code/item_90_example_02.py @@ -0,0 +1,25 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +# Example 2 +# Check types in this file with: python -m mypy + +def subtract(a: int, b: int) -> int: # Function annotation + return a - b + +subtract(10, '5') # Oops: passed string value diff --git a/example_code/item_90_example_04.py b/example_code/item_90_example_04.py new file mode 100755 index 0000000..6d6fe10 --- /dev/null +++ b/example_code/item_90_example_04.py @@ -0,0 +1,25 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +# Example 4 +# Check types in this file with: python -m mypy + +def concat(a: str, b: str) -> str: + return a + b + +concat('first', b'second') # Oops: passed bytes value diff --git a/example_code/item_90_example_08.py b/example_code/item_90_example_08.py new file mode 100755 index 0000000..09f42e6 --- /dev/null +++ b/example_code/item_90_example_08.py @@ -0,0 +1,35 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +# Example 8 +# Check types in this file with: python -m mypy + +class Counter: + def __init__(self) -> None: + self.value: int = 0 # Field / variable annotation + + def add(self, offset: int) -> None: + value += offset # Oops: forgot "self." + + def get(self) -> int: + self.value # Oops: forgot "return" + +counter = Counter() +counter.add(5) +counter.add(3) +assert counter.get() == 8 diff --git a/example_code/item_90_example_10.py b/example_code/item_90_example_10.py new file mode 100755 index 0000000..75ee1ca --- /dev/null +++ b/example_code/item_90_example_10.py @@ -0,0 +1,43 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +# Example 10 +# Check types in this file with: python -m mypy + +from typing import Callable, List, TypeVar + +Value = TypeVar('Value') +Func = Callable[[Value, Value], Value] + +def combine(func: Func[Value], values: List[Value]) -> Value: + assert len(values) > 0 + + result = values[0] + for next_value in values[1:]: + result = func(result, next_value) + + return result + +Real = TypeVar('Real', int, float) + +def add(x: Real, y: Real) -> Real: + return x + y + +inputs = [1, 2, 3, 4j] # Oops: included a complex number +result = combine(add, inputs) +assert result == 10 diff --git a/example_code/item_90_example_12.py b/example_code/item_90_example_12.py new file mode 100755 index 0000000..b0d98b9 --- /dev/null +++ b/example_code/item_90_example_12.py @@ -0,0 +1,28 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +# Example 12 +# Check types in this file with: python -m mypy + +from typing import Optional + +def get_or_default(value: Optional[int], + default: int) -> int: + if value is not None: + return value + return value # Oops: should have returned "default" diff --git a/example_code/item_90_example_14.py b/example_code/item_90_example_14.py new file mode 100755 index 0000000..1feacb8 --- /dev/null +++ b/example_code/item_90_example_14.py @@ -0,0 +1,31 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +# Example 14 +# Check types in this file with: python -m mypy + +class FirstClass: + def __init__(self, value: SecondClass) -> None: + self.value = value + +class SecondClass: + def __init__(self, value: int) -> None: + self.value = value + +second = SecondClass(5) +first = FirstClass(second) diff --git a/example_code/item_90_example_17.py b/example_code/item_90_example_17.py new file mode 100755 index 0000000..b77f9fc --- /dev/null +++ b/example_code/item_90_example_17.py @@ -0,0 +1,31 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +# Example 17 +from __future__ import annotations + +class FirstClass: + def __init__(self, value: SecondClass) -> None: # OK + self.value = value + +class SecondClass: + def __init__(self, value: int) -> None: + self.value = value + +second = SecondClass(5) +first = FirstClass(second) From 1ce625e697b0f71add394a023a4383832d2feb98 Mon Sep 17 00:00:00 2001 From: Brett Slatkin Date: Sun, 31 May 2020 14:42:13 -0700 Subject: [PATCH 05/12] Updating example code to address various errors --- example_code/item_04.py | 2 +- example_code/item_34.py | 4 ++-- example_code/item_48.py | 14 +++++++------- example_code/item_58.py | 4 ++++ example_code/item_78.py | 8 ++++---- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/example_code/item_04.py b/example_code/item_04.py index 9efce73..d1fca5d 100755 --- a/example_code/item_04.py +++ b/example_code/item_04.py @@ -234,7 +234,7 @@ def close_open_files(): 'Today\'s soup is %(soup)s, ' 'buy one get two %(oyster)s oysters, ' 'and our special entrée is %(special)s.') -old_formatted = template % { +old_formatted = old_template % { 'soup': 'lentil', 'oyster': 'kumamoto', 'special': 'schnitzel', diff --git a/example_code/item_34.py b/example_code/item_34.py index c4c19d9..6a82a35 100755 --- a/example_code/item_34.py +++ b/example_code/item_34.py @@ -77,7 +77,7 @@ def my_generator(): received = yield 1 print(f'received = {received}') -it = iter(my_generator()) +it = my_generator() output = next(it) # Get first generator output print(f'output = {output}') @@ -90,7 +90,7 @@ def my_generator(): # Example 4 -it = iter(my_generator()) +it = my_generator() output = it.send(None) # Get first generator output print(f'output = {output}') diff --git a/example_code/item_48.py b/example_code/item_48.py index 36000c8..5533175 100755 --- a/example_code/item_48.py +++ b/example_code/item_48.py @@ -54,7 +54,7 @@ def __new__(meta, name, bases, class_dict): print(f'* Running {meta}.__new__ for {name}') print('Bases:', bases) print = pprint - print(class_dict) + print(class_dict) print = orig_print return type.__new__(meta, name, bases, class_dict) @@ -124,7 +124,7 @@ class BetterPolygon: def __init_subclass__(cls): super().__init_subclass__() if cls.sides < 3: - raise ValueError('Polygons need 3+ sides') + raise ValueError('Polygons need 3+ sides') @classmethod def interior_angles(cls): @@ -238,24 +238,24 @@ class Filled: def __init_subclass__(cls): super().__init_subclass__() if cls.color not in ('red', 'green', 'blue'): - raise ValueError('Fills need a valid color') + raise ValueError('Fills need a valid color') # Example 13 -class RedTriangle(Filled, Polygon): +class RedTriangle(Filled, BetterPolygon): color = 'red' sides = 3 ruddy = RedTriangle() assert isinstance(ruddy, Filled) -assert isinstance(ruddy, Polygon) +assert isinstance(ruddy, BetterPolygon) # Example 14 try: print('Before class') - class BlueLine(Filled, Polygon): + class BlueLine(Filled, BetterPolygon): color = 'blue' sides = 2 @@ -270,7 +270,7 @@ class BlueLine(Filled, Polygon): try: print('Before class') - class BeigeSquare(Filled, Polygon): + class BeigeSquare(Filled, BetterPolygon): color = 'beige' sides = 4 diff --git a/example_code/item_58.py b/example_code/item_58.py index 023d441..e2b765d 100755 --- a/example_code/item_58.py +++ b/example_code/item_58.py @@ -194,6 +194,10 @@ def game_logic(state, neighbors): # Example 5 +# Clear the sentinel object from the out queue +for _ in out_queue: + pass + # Restore the working version of this function def game_logic(state, neighbors): if state == ALIVE: diff --git a/example_code/item_78.py b/example_code/item_78.py index 672ee1b..49f9c0f 100755 --- a/example_code/item_78.py +++ b/example_code/item_78.py @@ -269,7 +269,7 @@ def do_rounds(database, species, *, utcnow=datetime.utcnow): for name, last_mealtime in animals: if (now - last_mealtime) > feeding_timedelta: - feed_func(database, name, now) + feed_animal(database, name, now) fed += 1 return fed @@ -297,9 +297,9 @@ def do_rounds(database, species, *, utcnow=datetime.utcnow): result = do_rounds(database, 'Meerkat', utcnow=now_func) assert result == 2 - food_func.assert_called_once_with(database, 'Meerkat') - animals_func.assert_called_once_with(database, 'Meerkat') - feed_func.assert_has_calls( + get_food_period.assert_called_once_with(database, 'Meerkat') + get_animals.assert_called_once_with(database, 'Meerkat') + feed_animal.assert_has_calls( [ call(database, 'Spot', now_func.return_value), call(database, 'Fluffy', now_func.return_value), From 4ae6f3141291ea137eb29a245bf889dbc8091713 Mon Sep 17 00:00:00 2001 From: Brett Slatkin Date: Tue, 18 May 2021 23:09:56 -0700 Subject: [PATCH 06/12] Addressing issue #94 --- example_code/item_45.py | 52 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/example_code/item_45.py b/example_code/item_45.py index d627894..6d3c6a5 100755 --- a/example_code/item_45.py +++ b/example_code/item_45.py @@ -132,13 +132,11 @@ def quota(self, amount): self.quota_consumed = 0 self.max_quota = 0 elif delta < 0: - # Quota being filled for the new period - assert self.quota_consumed == 0 - self.max_quota = amount + # Quota being filled during the period + self.max_quota = amount + self.quota_consumed else: # Quota being consumed during the period - assert self.max_quota >= self.quota_consumed - self.quota_consumed += delta + self.quota_consumed = delta # Example 10 @@ -160,3 +158,47 @@ def quota(self, amount): print('Not enough for 3 quota') print('Still', bucket) + + +# Example 11 +bucket = NewBucket(6000) +assert bucket.max_quota == 0 +assert bucket.quota_consumed == 0 +assert bucket.quota == 0 + +fill(bucket, 100) +assert bucket.max_quota == 100 +assert bucket.quota_consumed == 0 +assert bucket.quota == 100 + +assert deduct(bucket, 10) +assert bucket.max_quota == 100 +assert bucket.quota_consumed == 10 +assert bucket.quota == 90 + +assert deduct(bucket, 20) +assert bucket.max_quota == 100 +assert bucket.quota_consumed == 30 +assert bucket.quota == 70 + +fill(bucket, 50) +assert bucket.max_quota == 150 +assert bucket.quota_consumed == 30 +assert bucket.quota == 120 + +assert deduct(bucket, 40) +assert bucket.max_quota == 150 +assert bucket.quota_consumed == 70 +assert bucket.quota == 80 + +assert not deduct(bucket, 81) +assert bucket.max_quota == 150 +assert bucket.quota_consumed == 70 +assert bucket.quota == 80 + +bucket.reset_time += bucket.period_delta - timedelta(1) +assert bucket.quota == 80 +assert not deduct(bucket, 79) + +fill(bucket, 1) +assert bucket.quota == 1 From ba8ffbbd6754b97d1ff696b3268da906d5138b66 Mon Sep 17 00:00:00 2001 From: Brett Slatkin Date: Tue, 10 Dec 2024 14:51:51 -0800 Subject: [PATCH 07/12] Updating edition names and cover --- Errata.md | 2 +- README.md | 2 +- cover.jpg | Bin 60618 -> 195526 bytes 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Errata.md b/Errata.md index 3fcfc93..fc99875 100644 --- a/Errata.md +++ b/Errata.md @@ -1,3 +1,3 @@ # Errata -You can see a list of errors that have been found in [_Effective Python: Second Edition_](https://effectivepython.com) by looking at the `Confirmed` label for open issues [following this link](https://github.com/bslatkin/effectivepython/issues?utf8=✓&q=label%3A2ed+label%3Aconfirmed). If you found a mistake and don't see it listed there, [please create a new issue](https://github.com/bslatkin/effectivepython/issues/new). Thanks for your help! +You can see a list of errors that have been found in [_Effective Python: Third Edition_](https://effectivepython.com) by looking at the `Confirmed` label for open issues [following this link](https://github.com/bslatkin/effectivepython/issues?utf8=✓&q=label%3A3ed+label%3Aconfirmed). If you found a mistake and don't see it listed there, [please create a new issue](https://github.com/bslatkin/effectivepython/issues/new). Thanks for your help! diff --git a/README.md b/README.md index a37db09..338101e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Effective Python -Hello! You've reached the official source code repository for _Effective Python: Second Edition_. To learn more about the book or contact the author, please [visit the official website](https://effectivepython.com). +Hello! You've reached the official source code repository for _Effective Python: Third Edition_. To learn more about the book or contact the author, please [visit the official website](https://effectivepython.com). [![Cover](./cover.jpg)](https://effectivepython.com) diff --git a/cover.jpg b/cover.jpg index 1495e76faea7ab30a2b308c18e75ee549918a91a..b62bd02c8b6b106f1de8428149dba2bfb9544efd 100644 GIT binary patch literal 195526 zcmd42c|6qL_c;EvuVu+jM%hDTEi{9&C6UU`BxK7@$S@SyvWHU0PPXhsCi_~lGqR0r zG0Y@PX7PRYetkZl_xJPtUpA0DgzyP+;Dkv&mN~QV-rlO_+-u-9$B?X0dRR13F9rb^X$k_6Z=09QP zp(6VK=<~0K{CzvYP-_`py9PHkGto1=sS8kn^QTtOzOJK7%?<#bUIBh)`Z^+(R@Nd6 z-#|QBfHQy+pyA-?@1t#Mdh_oHUfy2Le?R|wIh_7W2>_Uo`#aWu|KI=i8?&R2UjW#P z1Xe{SXMabKrV1eJ5ftD9wo|W@_uu|5|AMI*|H2Rcfrb9U0nWjHp9@^`_6hNGb8!t2QB+V+5Ycw>e&8%}C&WL% z+0$Rd(96-=&&S)(A;8%Q?EBCB{{9N!_^0Dvib-9%a^z?{V)1b z@r?Yt?cWb_4D@^aAK$2`l>p#>_x;~vyyXJ`lVWhGk^bGLoCpB#WC4IJ1qEKdItJ`6^8#PjQ=W3e+$ch6{^3#qM@M! zUo4FDjQ`dCe+--~fU8{LzprJYsP;*exa8RA}P!)h|r~7Ay|4AZnp0srI z42(?7EbIU^6%7qFEe#zVEw~M-!v1b7S`IqSbCE9%#->+0zn7~Z&Z_nx^0NGeAs=Z7w?Ztng8 zfsccNL!LZ~ihdpw8yBCJ{xTyo>(%S*g2JNWlG3vB_jUCRjZMuhtslF(dwTo&2L^}6 zCnl$+XJ+TnE118IJpb-xxEGnDUA^{x3@Z!s!1D zp{M_kF#0cq{tKTIG{8zj1u~3=1Aqafn1Xnjqiequ3gV@J|Fzacz}!v#NM)i9*%D3w zohL*}2S6@|{V1?L0cN4{WTx%)mKt*BapB_1=uz+H=$o>Fc+zJwqf|jW@c-v}^6h^? zyVk=ctjtzCGt2YpT2Bl|rzgv(OrCC%ii;P$&--BEmsIxgTl!Bm5#bxp8~gf?0^UrJ z@+%Xt@)qMx>>jFW=9~%xMr|Q-Z<9^y-g9a=v6_9PPUQYZFzSNJ;*a8fp8&MMXN*2h zH&&TDcse^ca^$XK%=wvwJO(e*=ElEgxx~yQT#jEobR@q!0i3sUBt{Qoa2ZQc5WB|A zeG|2G>yh+sFVh>t;*}G6ny5=^)ZKzHUhW~7qWr+1y6s=OejWB6t{;EH^H=U#Ok~IG zSP^a9Ahf0A!h^h+C%ws$RaN7=cJ`av)Zz`Vm@YMkWNu#n?l3}S@F!+)-(n<+-6y!0 zy_|`3O5-sJwM?5YHUHY?hd6?YXoeiT-gp9hK){dB%q$(?7_f#XfH>z2r4x_bG=XdM zB6r!Qgf~tjJLL5c3%VjAWb^?lYKbab4H4WrE!)9FfLY-KIv^9yQwPdpJ7{c`ti#u> zHI~HkUn#(FEA9DOGlq4BP5rbnV?8TTLr0Oq_a9TgRj@w28oPzP^^Q7T3al3l9J1Gg ztK?FPGhGY{l2k7KGCao|8~d(aiIe*M(~)2rWva8>d-kH_mnVRGG6TU2#z&}GiAhk# zN?hNkoBVilJ+c9@iO_P@ErHL%JB@i zr*sLDl_Z-J;AujzpuL^y&sBY#+0yn=Rvb-Zxx#z0k;xYyDJSx*ZY^x#(h%W6uNv`(_ZYt@cS#4&me3=X@dy3bsSu(+NgI4{385de(dkWt&~C}< z4j2FMOovBZul`iVq%~XWnslInXYo_FG~=ED(KJbs4|h`kY!ICgEQC&6&|&s8l%J&4 z(l7-*7aUm?_(^B+i_Jju4C0>50@CHFRr4Pu69I1_?!eD{HKG#TeJ`cDY}>y#TZf|2 zM`0P*3EWl9@%p##m2+BA*m?2dtf7saqn&x85Xpd?2)i#I2WwCksCkABmmyuhvw~#$ z+250y;8?oFxor2vBV7Nbm#yshqZeM#n>TzLQui51oYxLHCQkr2`2=AOLLolr_V`{0 z2kA;<%jB(e-)gy@rUWNKQ49j0;HJ)!k94dQ?6Z~s2v=sd9DPi z*?I!hc{bbJy!|ob*Z83`cHjKMJ?H4ayPuB74c+R(e{PRMk5UT{Z&#OJCN>nzV*_iv z*XvGzh~yZghdc-6TOR)j@Ti7Aq3r~~?s1UxC-Z+bj54`@d5^ZAt5+D`L5{%(T2v1= zO9$@{vSg*Nv0Ar+JK@7o|K8JFW;{a@$@2tY{EZ8~X8K{gUEi^2`?kA!QgrBhszb{_ z#`9V!?XOv)GVLn0H2vY_D4e!8^_F=W!jJrp1j(0-qlgfAyP0^S8~B`z+Gd@tjb zl~sLFvcC_ts@dhC>Two&I{n^UIgl#|%2-|l8}>+TM|Wjb87tFwY%9@6TKdT|W{o!D z!uPCSIh%1YN4Lv}i-WX(AqD8BB40;#TB4jFJZ?xf3hSmlU+zQ~66oLx=2-W9>}Kwo zRPMVLi+MUFW|L5*gDY|aw4ZI0`EQXV5a8DS6Yj4;h4E|RXqzxZ6gCySwkIt`nq}8yU(~R77j(PP=sQvIg&x^?} z(%qWSD|gbS=YKzu%V^td&s;!VJ^_~7As_a3>WM6r33YfkYR=JOY({_LcbRNpqIA0% z`fD3~Nvhy$ubRq@5b@f`eAi6by25&&2*uQK5)^c+0*u4G*+YB@&piw!Ts@s89?dIii>mC45Vfo<6=dk|1tAQHcY{MQF14f2oM(5cm%$GvnyXDoPo>%St9%ZF#?3fA^9Ni&bL|cCIY$O_q z^%+IH@n?0#3Z#s`I+EBshnL)O!xKBf{kXH#xEI$jtbwGr2}UDhe%;6#8xADbdsW)X zGL|ZM-{H#&H$o08p039lbXAYaY79zP5qT+ABgy%REXj+e2AB^ORqI{`XXkZ#Fn zixo;L-bL4#q062NuiD5Eqp49r=(kMPX`DF-eLi=#BvU|ot=;c)^^X_2DL&Ze^Z1}1 zNDM`lV7y8WU4i#hU1><>2!@-rjU5>AIzMi1uTANd4b6HEyr?+M&-`IIo^pg*F8e|> zqI_LyycJna8Co272E_dX&~=c{M*9h^GzQm&7Qr z>%Tn#s23IS&G8g*0_q+iV|Bju;8bW&R=PVnXJPhUOH2DFZ&f!5X720JZ`SWoSqR>x zGX<3W1Bs#_W#n3a z%YVMFrF5)7ZtS>(?7U53+YXnXfieUOc2ndCp%R&Rw~4Ls5i})7Nrz_Pi&t<9dk01& z+R%rWRw$mLfMtC+trmaBr?Rbm-4lCp*C>-B;TNP7B$iFtLpkonEH#$y{8>h_=goU# z!t+)`HKaxJOeV8utqol~n8OL*Z<(t&KcKl#mwC%y^amY9tm6{lO*d2tG%nZ#2y)!Y zo-REC)%m6GH@xS(s*BRjD-CeUFaeaavsgnWh--Kc1ZV7>fNV-iFC$Q{nvZtoGL5^0 zS_M`Rvi&u2(Tc?l=L*egX_^dkwPKe`rbVy^Z0KPMlO+DkVIbjZ5K$YUginm=$z#~^ zeSN@$J;!cmJuk@y6-~2rNrKjegxj3oZ>)uTQwA2f8_7+m<&+>)ZB6GVzlj_8{^)9a zn4@N{W!nq5%8!Su?wJbk{)`Z6^*A*d`U}JMSvbk%%vz9tRp1T@Az7lxrGcl^WsVs&!#fGlJ@{8EMLy1-uTu1-Zn?c_#?{syQf2%S!*BS!!4!x z5}6|g3>%5Vi@1^;^`^&Ok+8C2th&-Lr@|7d@k!mt&5PD|Z(f*)&-H0#4iQb7A{ds> z|qfv69S0Qo>twks*E7r3obQvR=n3 z_nUG4g*C^@veU$>HL=}Pr_aH^#*&_}Q;|xx(p2+Ly-4aXY*)>X+!!KiYx#f1T%GfJ zE4>A8Z#n^R*Am*2nNNT&e@K5GV-APyb#%mpvuZr?si)!z;QEO6()^1942r?gELHYV z5VtAQRh-OG=&kx_*z&>`zd6r|9dL|SA)%Td9Oq8}CLyC{zWEovFYeHX^9E;6Y04lg zEWh8%O5NgBOHn>t{gJJ4#a>DFer{6vC*B|(`rU(LIpTFt9w zh`~$_dK52NSq^(}w}1V10#sULo2?FHl&++&#l@I0k}xldZlR>mEq{Fb7#>+1(t$kd z$4hqaCFj+$b<4-XIE&8wgadmu*cbDoyKs}nZ1feU6Ck`RFok|q++g#Rh|7w1&nQ8B zrjPCU3DAUmdIC7s*I4~D3&zYYT6^GTZ)T=+Qjd5Ub#7=-R)@5ZPx9M;(C}^ifS^5= zpM`%x3AgLxB#a*~^`sHpmYZ8pjm@D3+3NL>J96+H;;c$XIkHL(=g zDT)N)Q+t(BPgb+~r>&F|pd#&1?{!9kKk;3c=356-RzQK4k;rz)M~;SygfBqVW)N~K zat~&@GoeBsp6~A6Zf{E4g(^#_uG|2nV9Mw)k6a<;2D%^Ga`0T;8Q1O?E>~;e?;yg+9mI0xc99^RWrLm+D5XK^}47i&x0 zrt7U2b(%KCGW5J8;VPmUJ+kZ{HubrGKyU^b55fHugV1DV+Av#YJNC;)y{nn$`WY@s zPNitfg2xh-8j|=D+QJ;F+SC+1fA=lpZLgw;bHjF5Q_Y)4@2%*q#8c(<$&r6Nwstzs z#@v0%`_@xayGmNc-%2oTdS*89BMue9o;7^> zXe`E=minH4HC?!XN6B2fc=)u9<}(MitTpnN+}rcb^L+h{NMGSgIQ=Aq15q3y|Ex2Q z389A911${iPYA={Orz46I$h4s_R|~wY9bN5k<-svDPogr+FRMy6^<)^vda{{@m zCe*tnFBDxedF2z_|AeAZiCR5mJKoG2u{gYf(>M&9Lb<~T=a-&Bsod1Ywv>YNIt4u6 zFPgA?;ktIA=cP^&u7&S>y7)^d3+Wo+3TU1ckuwp7%S$i9c@p$ylm(YQ3^=6Nspf=< zoj0=CW{DHEfM@hw6X|4q!G8rd0EunCT28prlbsdSSe55FE?@n7`x)fBlGV+_U)-a* za{L!GsOgY!6f5E!PLRS_LzLe>Ug|Lc%|LHLf_*#DU$3_KQ(tMBm<$(BlX+W$tU;OL zxRR$}u0G&PirD^mSdPi#fD=k>rMw$G=5us5`;RnAD(hU~YB8E63xwjU25hkAZN5J4s5Dt|ZuF zWjz6}`osSFP_}%mq>T;FuLGtxUlcWYnT5|&nJlE|zN%SESpAytp5U=MnuJiRamz%D zlapgrXj>*^i#X>k+tXg_HeBRX7}KdPi#q@G9is3Z``GWc@J@8riMPe>sTM#xhj9g%58d4 zESvAQH=D|v#x=CvO~+OB1q%8!=`PgyL!LvXrFaf?FuhqZW`Lk>b2$^$pk zgOylcN7G-1xFvCThcF+}dpt;bMY|g}TI`R+SuZ7`AAiEPbuZP`M8*P^VPbgxrxl2sO;rJ2%^E8?&?;8bUp_66VaV z677GP6{sK;)}f}4x9ve5(Ec!6Y98&x6~gdlr8JaJ7b^~HC@jXyd9gCMww?}oWK@S> z-PjpRa2a4);}1@^`hGMh;P})$ckgLi-Z|9xt@DB(!q)(#N-jOFY9?9J|6TCup@7CW z@Tmr}#FZkmEf#lPj`=u#ZL?0+H}Ovu>8Z|oY(nPM4)4yO93e>)Bvw36s|QAUW#Uoe zxe&KUhp8FuN>U~l4aKj(#r9DQqLE36iNh!SaxtH8be=IRz6w=hJAf6p;O<9vF4fSl+nG4rcGYRV*?i`J;{Qc=uk>BAbcta zM)sm#gf5S*)Ddsmw3Qs?N|(-0Af)+D0H@zI!^yLunB=H%I?5dEG^ot4r`T*}^EBPc z*YL~4n|S(qY^V`AG>3MO(b`@SGuRC*=nSGQAQz(C^5(r@X!(_-PelvCjy7)$+hXpu zw)ut@>br_s@Z3m+9#F&7mxfXqvNs-I5og|5PR7a*RSMQwk^+RH5xBS03<~1OfKO*@wneYsU*|?eR6M$hsB0p$3j1x0@uO%^K z%ByDm`Mrob{JOr7MiG2v-(n_NAJivQbPyhJ!_~fagtKf#Po92%u|^Tb(R-FAAD@SH z1F`KRBogIGkb3evWjNoaJq#Bxqky)ByD=WFFYG#T=*(MNjax42h{~o(>xo{_7DpvXS$&EXipRIys?J&!g%$7oz^Q z?WV$Dht;p@;@OtIPX0eOntt$RBxRnnM-}zbb}byaSNJH^0i$S><)fZtGz%uHb1yc|^Dy}-uewX@)H)wg)66O)5o$tJ z@Gu3U8+gD?H~~OyqDhEAdYBL-yt`m%^w1<|e?_|lAMUe0iMO3A;5@BZvqpc!EgiqH zVzG0C^S(>Y@3<0tXW1*Uth>mTP+>}>w*Rg((a*5!{Khv?K-KHKOvLsKXiIp9&%$uV zRK)WLwNLn_V!TUtxY(5P_PpSg1{NdzCRFc__&0~2zE>8gIEd0fE@%YSh=9a*qh4y> z>tcTdhF+nOJYr?JS!`sVG02unbF? z+FzD$(qkuuAZ;dP$9Z2iA~YpoJ#4X{=OV<-Co?@b!F=L(>_&j1n0%S7zE_o&@ik%R`M{N;8aM7ft^Nf4WUt$@@T=sR|G2zeBPu7iMxeFXQCm5Ywd1@ zt`nQhb&(uRQ@g^Rsr>%L=G2u$tQf$R^LTf-;F~H^b*UM)ql@vLb?na-!l}h+8o6K| z;)S!ICFxdkgHGqzZ=^nw5u5Om4EJMRUnF5(p70ZI$GyzFYvhGtlK>&4g~-N-S}Z!|y`9t;X3C6PqX zVFbo+7@dE08>noU`ep*N_C8lzAQ@-rzHOqsieh0^DNaT@{l;k(O< z5y>O(iTa1MEHsbf>t=6??+NSTZ^*VJpnTN%!O*C1)3LR zpGSm3red&xcq0n4A?{6o6=rF!^m7M<5V?H0;j_dCo7ZwAt>*s6TX#YkbCm_H98Ilo z`V8D8y`MAa-Qo&+_wWnZMv2zz>mhqVxiVZy<>j`(fb@83ofkEujW>BV8AT3HU`kLT8ny7)Eg_nSvzO0~&i;2IEDzUmJ;5sdcOx9lOf z=Q*owRU{ z`KJq+-e`|Y@bai5U%Pm1b15?BvsM<}Fo*+OM1`DUU5`v=Q52??3d$^;l?2B-w1*W1 ziyGhpf6Xm)lsk)eJt8Ux$tm4vW*2jCbS5@GncWfhK69)tP80jqAsk>Q_hOtJfNg z=3}ifQkrkvs+lf{_I*h`{f(xYY4i~v%r*I$KJ*=_9csq~q*4_uU!ykGVwp=na3`%Qy!bd(w zfgM|6*gC{T>o{j=2MjFGuFim-+ULR-2z9y8aZT=G!ks$;o`aJ+MJ={r*)r*uGQ92^ zd}GpCiQIa-ANgw(tA6oE@qjJpK-2#lk%gF;f>lC;m)=V z%DK-qOKgPm)d@%pWHxK{RzeBEeYj6?Avh6ZeCCeB*`#+iM)kticL&c`dXL5F@lno< zqriy7a)B@}J}4UIvBW+bJlbh((mmWNtK8c6EZ??jA&L<{z;iv-p;@qa)+kxZktThX zQ$D;K@}W5i=_SwSk^S&lm36SAHm<5&O`xMHgvD0&VOZbg2Z{Cy~zf*KRQizwteuu&o<_k`Nmt5^INfF(uf_3G^RMD4W9;Ycx zqoYbCW^Ro7Yf#B`9X8pO#`VD(Y6$XKB+gxVf|ULa;<&%-ld=&Wwo>H(GnYS!E)ilH zbDwxC&FVS##Z<1|0VZD|<8SQY(qt5B`3>mLJ^=_~OFO=pQ8dFX+)`YnSCNBvforBc z6wi~ZQuPD_yEx6Z;7(Jf<&^IBYdP^1sRI5Ch0|Iv0@DDhp-^{JT@1FdDtMaMlPh`+ zxgux1)OWo5hQ`Q^lU!*0s{m!&_P%kd{_NMG`g5>0yH3gdYk@3QwpWWkUm6rtR5U12 zGr!iDk@D+B5x0|O1$AlaZHr?eWT!T48Prp_YfG~%hczSBOt6QU!4GZ$xWVQncp;r7n;c_ z4aw}mCWn3G((p?V!VOJ9OnIQ3MJKw*-(4WL!liV6V4Bm~fVlqg^{#H)+0nJ0*t=Ni zY*KS+oY=kDqUft_<|><^27Y$c**0GDu+NKpucf7p7Mo_~hnxR|KprRaB4FfpFdoPz zgfJ(ULq)W4#hUldQmP!w>@R$Aru#uH)j)>2-0!-4{Q~z7c>BOSa~3x`d}*nXt-B^} zk%wSKybwN`WgLdvSnh}xe(h1_>6C0SWiOtDIaCR>V2|KG{}32lH<+FO(7gV$?cDbm z26Rf=E#rqDQtw376x}Oms2g8sbCvhLXsBP=90rGv+M#Hwa3;y5P~51!2^UFqrV)19 z-8GS~ZM;UJHgDLg%$`Tb6rXX$Ho`YWhQVU05y|esYkwYpYVB)r+0x+pg1!By_?MAi z_fL&~kx@>X1*k`S0GbFomoYnYi`K;7wbR=a82$uEP&St7hRWdSE8cHqGlx8J&N%pe zq}p^t+t#LAw80lqR5MV>^6P~>H_ie}g;&B@JUhVZcS8RLNs3+V#m84-EXFGXW!~7q zZtE&a*|fPj8B_!2B24$5GtqMNhNZ%R@X5Six8;yK5MD~u!M^R?s))MXb;9wdP3az$0U>#U{OJYsxlipn1%ABAzTEPSsYsUlf6PL=tCUeV+Djst+PSLhN7wpQc9v@YU9`EBTU;LC! zX~dGQ6u3S#9ayfA=BE|zd19dpx{C<)y%Y1_32hdWfpK!83ZK!5*xs#prKjdfzG82< zmYptAeIC_*!7K$-&64h5%G#9|SraTKE%ZV(*C%wbpKBGpmRit%a`mt$02I-_`={eb zC0ZBY{+R|LYlpmZ$)I-lCDpBiM`g^xfW=i^ zAV9T44j-5tQ{67=Q0Z=dtb3^YC5m^3!)mkzK+FfEI;4vL^WS~|Y&6_ohK2n5#%|@0 zb())lc)Ux#W>OHLZtU0>u7c-o%*cvuT(B^Uxy9EIe`MPFF^Mh9dpFJPtmB_>c#8aR z|E&ueC>}z`3DBJqtbPJmPi~B1bDR=xWm495Qd8D$%qu^qL*KGsRL#{p<^S81z9g|~ zJ6ve8U>b2HEU;uzJMSW92xDY5AD$%|9xCht}h1n1e-wN3uu zT(q@upGo=Rw|rlDTwk%h=rq@WW#s42n3j)HRATpDsgJ0|DYXS@GzX4WhaS%q*&rMV z{mxF=o>?zQMs)24q6P&sDvYbwX{he;EMF)Sq62`-G~63t*3q~7w{^LILXjW7@p}#4 z@h1Q`=}NK9roAxUJ2rqQcpXfDbE{mM@8mq4Aawg{S-i%DGMW$7)bYTl3il@^s0!$( znuYH_)qY84&R8^aE_~+_!tOtGN%snwZ}ST2b5Ge$@0q;?}L%{2Wo++*@2u`_nDk+uK;PLr;^txi%GVVib*(e<_a-Y*c8p%8rEA za~g+tfo7a4aybQhj*JTD2tGQD!+ZA#nmq3S2*?ngH(_O}tk#68zl9XSogt7FXd5<;E=z2L? z73MA)mV}0g?%!@rsW=ARsg;6vV^Zl)SF(@1~qN(K9RaGE+kT zT0_Rrhvn37skhNjC8V3ahoy!c_6%Sd@_)6uHrt=jWL2sEJaaCq(R4BYV zuL;>LA61KZLP)@6^$D;&eRwa$qxo19XIbfeWUnXF!8hNK-p>^V_0sd9Fi-~IjX_uy z?{YiJSJ){{e&bEl8umKZ$2d5-&H&6Wz zeZ5m7C&t<7>!CD1dL}Y)Q0!)P4DE{wny32L>|A2Q#YskZuj~Lg54hB(DieyHqe1cd zsV$3is5OJt+M+;q@vw zT$$W6uM==-?psxVB5ig)eS(d{iY z9YL(Dy}m2DM+Fte+N?&_q+TzgfqCgM<6kAd?y#ZXVua2^e$z)S9T|<^kZF0n9NW4 z`J+Q7!W_tcJUKrwITFU}S=!RvkoPd7x;oF-v2@#1jAwjUB@4|GlG=47LmTbnQzy;O zum;fs*PH}w`K7Q7h(!fS7oQvf=2IlF(IUGbMRV?H#$hGHxXDFFwctz}&iDdSs=2c7$H$i9JW0cYT zI4MzXo?d@!Ca z&398;wAQ=pRF-PN`7z1*vgROfzNwE)0>K3*vx@Aia=eGz>#U*QwwFbH^K?Q@Vguet zw&V_t^GJ(L^Z~)+YG#QU-z%B110sOWjIad$>cV<|9*>V4R z$f^Cn^-ncMWYDkaVdt{SowajNf{@4G?a-VIAOwcOPlI{ELm%IMd~^c%?G1RATxL63 z|J8-7YK$*mp(rBQKv$?bfr5;NLo~%J2r@mHANu_z-c(s%EuviSjqVz!T2zKPrE*}>0>bT;j&nO$|NBMW6@K? z>xP4iLV~D*znZ=(dA%xba^c3B0qy;GOou6p#)S({tzKd8-JVSU z23(PR$4y+rg;E%OOL~d21dYRTat9O&TJ1g)P|*uUE1ocTqet6t*+bnl-qrL{CHLQ|P)#M0oWx9L}z6s1^~!d#c4&CgeXKKz7X z<94sACX||i-n3c0Q&R`uXDs-l))gdPFKV&>dgYz+t3~AyiQOX7BCN>~D7SLh!(?Im zSwi;u?)1#w1kmp`XZ4nL*VzKX2IM zCxwCD$Wc1i<2JF!LFnmpm8yn1!z0ms1@kL{iM3zW+5+xLiU7eTv7~z#6hFMboX`*2 zLvLb5FX>(^4)sOg`)=JRv^6|;Pm(rTgp>XW^Vf+$ITPKI6u%65>Z ztIj5M-bYDPL^Aq&aE&Si7}?DiMNv%NaS4ifaDN1H8+3Q*+m&rdYD)ip=MsOn zX~M)gl)l(mEb~aUTI{*2nCrv1e!V1vp^s`GQ+)exkWYc<71Eg{J3<&J;Cpd-B(82V zyk~-2xHLYaKMM9C@olQHyXUT#XX5a}>ybx!o5hDAj9T~Aa*A>{RyRnT42Of{w=gi> zEgTKuBwa@#lN9{Gz}>MQgID1(XSI(eIs< ztByM&orkJ{7Qaf4q)X-_@5azp?CZ;aoDz%YrB;jD`YzX8&^)MVfw_V=B`WR_=y1u) z@;z9X9g@=oKhb>+-ZW7#<{fZUn@Z`aMOV(Sjx8yD`q@`EA7wmzi0l^G0P*H>)?s0BF zGGlPM^%zz_@8$d!x_IrS#?qcz7VTKc`tIW&N7X_1rW!p%@8sFcRPV~~dA-t{QS%4c z`~onwx3bEKHRbYhj0R2v+&@nM8Zg`pL7zE%GhN+&g{Vdlw8I}9=CKB;`E06s1|7PJ zk7kKgtM&@Cnu&g2?l}E<_HsR5=Fov$(!mlO4&y;M6MQj9MmMvcr;6m(n~QDTaA)-{ zqb(ZEbIu?t1e9_j)7{@*>IFg`zQ*KnyJj2jJ}YoPPE34W3Z?^OW;WN)&)bsPkYTKT zvSw>qch+T`wd^C9YlBu{0&s%+QfCl)y#kuxvGzy((Z}eK%uBJzsi-Emo6Mq`@9t75 zsIF3X?1TE-7F6b%2YUqRe(aSXqH1txZ)P}4_GotyMbuO8u+LaWF;fZs`2mOS)q3hB z_fx2FKu7iMi%?!HxK3&3$=RUf&3l%N=lu+6lz$49*k1B#XlTZLO)fR@K>7&0Fw5^zWlxGlbnvme7oll? z?!^olY#J0zHp-b;Pgbksc>Tm2|H6_6cQ3Ao#;qafhKFU8T0a9kjuF5nP=f*$CqY6V z>q7J(YFt3f@&PN=ZR>JgTitO*R$@u?7B%ny`X z>y9Gk7w0U177zdD!O#3En~NgkJj&Hz6U=uQpm}>aG4v*W?aRnxpJ1NnV3hh zg-MHA#iTD)YjqFRI%j)m5@GT1dP%GdPR%f;2OzjzCADb?iLU1DbrA@;&)8 zZJgbJ1u@_9hjXofnVJ)a)+qbmyI9pVpF1@1HC=oBZg5Vhz)!!;-1FZqu1qR_(pwS^ z%voz)U>(e5(i1BGW9OIntCoA{wicu+4|i{mAmiN&`X4U@B_jAM2;C+pK$M3LElNK;VLh%o=^s9>SbX?2C6=Aj;oE4tLRL zz`g-#Za{v~A?AVNL%KntQQ#e|o*)Q2NyEF;4aKBv$1)h|mNq%x&KqOV&|3d=Y9V1z z){TX7u&-S4<2`8`lxrIxv!cWM!$GC{)SV>Z0a!yxB6luqz%}mZAw@n);^fkO!!To+^ZYM*@HI zS=hqIkhqq18~G>|q!3Bn?o3xRJNmn=X7-3TV(dMisITC;ObPRJ_fVV%u6HfXmY=Ul z{b6?XR!R*2GA5#yJm!wX`MK=mW5|++PuiYtQ#imJ@fh2274O6u->o1mw{M%%MIli4 z9_ZBiLZ!V`gmHqiNCcn7#?nqPNX7f)7f>*nv@?*R9zJI;)0SsO@WdNoGu8FuRwgzw ze!om(Ie(Ed+;}lcsj*EL+OtnTkhJjTH8NBFQlEm!@B}(N)N3g>%#~M~42A)mVaxfb zdK8w1crKXb5KeHzpD`ljmj!!+r%h~stft%w)62IsaZjO3M?*t`bb7|7=npwXfq&bYO7>L7-A{Zu(Ly4X-T zD{IrU#3%l6-{8TcHOY^OmQJ2EE7q%IIRTBD#foxswtec-7wn;-=GAT09#E;tbSu2v z+H`5C5X+-xwHYP*TjxyL_|k$>d)z}98MQ*FL2{;3KErU9d9zvI6LBVxNH~qAA)Y~x zAirwzjI&?3+@mVk-*Wog)ao-Ij~pf9g`Z!a>ks3Tup3`&8os z`?k4RyI&m7^jg5$a}S%~MSX=~X1+;&9@}K8L~nwOKK2}u2V}A?c-;pTAxuwxMY>k8 ztuCWmOfa%XO`PjcN65YZIc@rA?EYl>h@+66V{~lK*YiWQv=;l-Nn;w!`h#aDW3EK5vhATdb?yL%xtC8hIJ7Lm8!gQ8L^YBU`6bfRJ04cI(Y5eo zzo$bGmsAvB87SBvxhKYHIp2=C<}GCUC)G%^7ExO!2#B7mC+>mS?gJpfjF6poQDEls zQ*|}$Dn8VPz=D@wK8!u)-kS|*YOSA=%aJo%sa(MBik`pYai;&-hK`~BHTd9+U&s~l zxCfS?ZwK@|i2X=2luZ9(C{)**2WG~6!$i)K2b#(Cpf}`A9spaefir_ij-bq23G0u6 zG9eV&o@yjG&^|MpmLOi8YCz{VwLI7svqzYC^X~pYipSZFawd zFbBySKME%osDK%1f<2(1x;B0wcX9VC8;!Ne8X{4A=z{(sOfHu%)iEd_@iD`aG)3ov zex@P7dFj5oOCu=I%3;jNPA3u#&Y!}BC9;QqIRU6B((cf+GaYAxo3CeKs*>`&1F2%P z8e7~hie|#4#CvoFF;a%`rw#z3AQ#?E-{4_g7G7JjlL+`w+QTF}w(6U+i765Yi^_#e%2vr$b9PzE z{a(vhL3?S=6{T5Z4O*@S=mCXF$g^MfM7>jf9A}5*KK!~NDS9^4ln3S&t{6TCtCxHM z;`wapG|Zt&!;TllsVpCF(IGmeEK#55?smqVcVD*T;R~A<*U}d|oID$JevCDslyyT9 zq}e)6&{-7599+%v<5r^9c6JbG!T^@`)`T}*0xtw|dMHY%8Tm>4yZb+kyvGBw{be0p zX=P5svW@rZhewAr9+xsXas1IvTDmn!m%QLTuYb5eeoaw8oFYe(cySv&F=mq;^n^P% zCp0}68(NZyio56y?KFdZG530IBf)2{SR;1$V2aO(-e8oPfr>J6yg}mL?h}SiQ!c@W ziAwgQ3t_xBn>|=oOARp=xe~BVxXXU~^oyYm|LbQU5<3r2G1R-C>BX?%&E@^#1(fgT z&Gm>i{lhya06mT*j%)5^BZztT?olDkU-hqi3hmF<&G;$UhUKbo%l69c=`44P+_h@o zye*)>&Fd%)yHXQ_T3(kKMSGi)t2CuCkQdDWJ%ia|lRg4WBt~?gc|B9;Qa0Xu3U@HydmoXA*fV)HrQ* z9lVw{oBWN%Jl4w|0uFDyU|GL|UabGMgiPGl1JSz%za=8)AoKd5^e4#jlFsDW;FEV+ ziT{JSH;;xg4*Q44mXJ03(iF=lXn2$(bv@7(nL5 zO-KJAPGN}1E29A-b(?RC8k}tO&lMotLO7lmPQjI8`OdY73!q~#cNfWWet0<12{>+w zOV}io&)^O=Fv3u2!(p$Te*#mxwW=3g`#DMUK<(nV$tNy@3FBS#D$On3;a)XUCF^Bl z>F|tW#^*dZprhJUUL>8Jljw(o;V+P;_f%+kVLBj;X8u76ODgWiRXo$cwA78Nh|Xpl z5cs}CM6M8^n7d*Z-Ibag-)JczI#pqQh}n=O8T>TGBCLN=h%WyN#rg-8fK7n^#0b{S zYi+sPh8t(cB+%r)53kY+{GNEvU&^`j)*SXN^pQF5&=rS9aqDDK1Nn>HFP&U_Sn>8; z2%Q~Cfl_^e6Dkh-j1o5BSXQ8gbV4DP>F5`&h|(DgmcGglE5XXP-+T97N+Ac7KFNIh zt)0OhEAyK|=QsGt)$7gK!crf~6Z ziGn8kvwWBQnvY@nZDRnbR)z5BjbtfbBczfZ1@a2PX;XV|j0)}D4 zQv@~68Fn}szC~Xd{|j-bexJF3TrT=22BP*C!ts9#Li{|72H=GefqR!DFu|3wjEFas z9QS?Ac(-|Scg7X6d*D1H{>&e4K)ncBRQwQhJCZzpKNcfCq-?35dORJ+H*j z+@ZGQC^&4yeUQSlar8$pm|K&2$qg#0=>@rY>bJOAyd;*|bRQrLF!LrOk% z+XMZSRvLEvCkg_Xhukj_46SEN+v|&i#CmTw0@@?nbz6%!bq7Pn-%lQu7M?xRA@od( z5&1gE@;|~>_UVyb3HKiCd}eIuInQ(S!jgtTef>+WP0dYrvBIYuGHfC1gQgvu5%3ZW z@iGGNf%>6jdF?(q0|nfg9&2QsR#L0kvS;4!Q`N~vBBx@w2haT|9ePOXxniO6rTR2| zgZg-Bk$eV06^o&q_#SFSghjhNe?&AZ!Vcim$l1N3@#X4fO87;?p4&T@9#uJA6fO&A z9I+h6<#(Wnpom45kL!6ZsKP^b!Eq-U_UmoG+`<#^B5Bn-+sYNIw+_{h>I_%s6G#;`Oo$EKPNn`kh zkRB!pbQ1|V5qoNmA9?GkM1LQAEcn2VP$L%oY9)!_#UpV!Vx01bmH`^}5aEkNTlmbr zF?nU_wr+sRkx-*?SBnZ6`&eC3b&=fWnwGeAWT<+6ujEdXvraF=Qv^~wwpC^sDH0Sp z-WA3(uA=GVV4aPh$`BMBo1gz~`4?jFAyOdNHI)0)i%U$>5H{$TJ04((BbC4g&(n2( zT9eT$KJ*x3*uYZY>7r_k!zAe)<6eWE8MFAY7;WG0;RDgU@R?ye$c`|-p+x|5e7(5> zOU7_(e6IK-_ZL#9q8w`SQ6SYv>cYZ!MNBPPahiC1WUOgVsRaBn&c&wxUWs<9zq@%ewbe{7+-=Ii6Y+?MalpU0ET= zNJEZVhR~0#k_SBt%waL@hCj8ZH$~()*JG7^KDe(l4I%AP(*2X4>e8=ME5fD_HG%zB zfI0ZBk)j(ms;9ScLVDDo^oFqtR!P~%H&9sFr+6mETRqyR_?~)AO-<>|zYt^eOg5eC z8Swlep?+j1FBswa^3gEvpoKA>=9r99@vlYS2jaCY?mjIUl(u~LV$t=>zN5|;x)B(N zHdrGlg^^`}OodSIcDT(|`f=i7ETL7~1rgvqmGnB$X;p2`cP zhMO%X3CJ2RB)#T0F)^=6dS}i2Gw(f6{Lsnez#1aUgFx1TNRRULM<5UePa7Hw+TVn)HC6xV zMcaPXL?+~?b}&bs0LtL<5X^?h2T6m=`<;G*Ey?3pU8To@;> zWf}OeQu@xLQ}G(ip|kWJg9~nXa1ZSUL&;=ajf09G$s1=NeIqrmmLhPaw|<;ZGuDRn z_`EKa?`HOozx=b+%5y2fiuZwF7@L0GB|r4NaVP)K8MDXoSf2jVnfT>%AI%4FYgJFJ zZ6pL_GA*jCnXg@&6HH1l;u*E`fnV0DfLt)KirBE6OMcBDAaK$qG3&ku!~g5$saGe_ z_DL>t-uphCf?kL|yU>~J@%YQm6A*^0=j8Nl-4m@LM}qhXv2Xv8_@@5H<*A@)f^T5T zVsIbk-e8(x6wDwcmW|9UM7z`886LMHZSKVqp`9F0_z+!=%bfa_>6dVG24(wFAYMv^ z4;0xZ+P!YL5bsmc{~Wms(uR?)RLMz0W-t*)+XXfsNVSbo)$aL9+Qh_rMgPvx6lb}n zQhyD?4}qYYWKwnRdfq=}V|pV8!i3(qQr8eUFsr2WTG~eT;`N^brVywxoEI3w<#t_5 z@OpG%j?)!3#-yJm<;3{=6#MGTBSH2TGcBO4UG3y0pV#8$gU0UyJQI1v*pDU)A2>V4%oM;xnIb;PF$R(KMC zdR??!1ZXfkgNjGs-lI4*EX8MoQvN(?AYHya;HT)L`9)+~D)#6|r_`GH#k0YitG;X6 zaUrL7Wb#$#IW?fahGJ-dln%9 zZ2Xt1`=X@fPqLQ}8$eh!ezCJ39^bR0WwpzIg{Te|4HA)24UyYDCzLSB-#AyE*pJXQ z6PjYpwv_I2YP2SGUUXRtK%*ei1e>%OcKROh0plt&XU4l=Y@z1hG1y37Q{P+kS*pZQ z<-alP)P?A(1wML*7+f9mK32@h*=p9t=)Su|;RhI-^)KWOVm7TCUTfC*=Is*%*Id}) z&Way4fTRDQQpNYJ5vRs>=HqZyBgSyngFIkkk1)7Az$hYGW9EvTN!qd7q1%ukku3ww zCE1ZEF>I%-P+)F3k0oKq>Gg`?MeDi|*B-Q3>;<&Q`oa*Gr3`SwgGe5DB4 zCCVI+2vP{HW z8(uycpGK}~GpcVt`wPS0$7f`JP(Y%@9u7h?9B9uz0`5~fjOS;VYP`jBE9aUPlEL22 z2h%7@*p}07Vkso)rFycd7xgCsmkF;vQ4U6onbv5)OVKbBlMu3L(u;d}+WA!w;;P(f zlC*lt`jO;O)ki0ATB=2u>G-bxE5tn{*ZaHTPcxnZQ6$MFmw4G}cGf#(`8)hfu-A)Z zy}3PnCT<(NT_b^evh6L+`Ga2#+Ay;I2#NOaFpzNNRH|uFV(UK-UZpSU8_=1G5=VlC(o{-|As1C&a zUTkByL15AMpV+miV6FEhGtM%Z{$7FJbFT3w>&J>mUYW9|799WPP#Ee;u3z>;q6Dee zNz;8Pb$b0tf3mlE7J9yo2^UYSc~UEe7S`bep;ZGjXl_=$~)Ws(m)Mg zU|ArF=(tVR6~e)i+L?l)_5k3B4Bm7wJUJ2S=%xI&j65)<>!XPGcDGGCgOVyU*tb=7 zIc{oD?w|49l|7E^7VAm)ZsAK1<4TP4q{$@?;hoRl$SK(Ika_LPYji2y#f0HH|K01? z?#N1nL%?z+n->}RzsZO({`)<<&aNc)2th(l%kF3H(E+=szvHvwA!w7U*LZF48ju0G zImAfJ8%B4pK_1bT=jO3_xT^i;PlBxg*r^} zN0$=?_#wxRg^VWCO7+I5J@>-JA`-{49^QOtEz78A8<8V#ool-sE_L+Tcr{*b!kYE( z^#8biqUVrgZ3=FistBN!1SZ+{gQGVc>Y?3S{oRl-zokfcu|G4dbLg;q4sjPw^ik;~ z<$=sx0%;APbOZCs-h8Y@vSlSw=$j$xLC#&WLsE}0(j~l%QvUof%;B6t7P79-4rzj( z2l%Fcci@PQ0Cfh@Ie-v>EuEmfr?L=5`>ykmTY8{a2mkdoieLit4c9rt$U-|V))p=aZJWWzW9!svIWyjqmTHtA+0w*N)?_A#UBfV}pHbBMsO ze>u_P!1{Ek){zL#un}x!QQU`yi6?TfU9p>cFpw(k1jb3cg5>uikq7%l`x9C{Tq1mi z+?^>BwRx$gOGSlE>oVjx=;cGhQ{Pl)5>=`T=C_(P5AwUes1NL_$rqXwwxCDoAu!z3 zFj;02O%R~iKLDkP>7FEc{cW~TX|wt&A-Q%Ds^xRR$=d}47nl14DFr!(Ef)ig&@fv> zr)MjOnf?{5uhaEG2z-n{@Okng^s+`>@_?E+r&gM$pXcL~96Q@yZ&u9|!morTb-w%y zxe66^3j4N5yka%u*Xt}vwpnU}buO@w;h>u0m~drWsmJcZx)O#K2SH*)+DUd?aL&?rZ3TS&@qO+A_9l$|0H}mg%G&> z#YUMf6Y?hDWQY#h5ephKwRoHtHwCeQD9yAt#XBnxzuD^Y_uYzNc^$(Hv06o6O+Q?u z2!oUT5V$;8RbbcAFh(j9>1t{YZ(U1EYyJ2iA3g=ZM;v@Em*X#9uE<@zdOyue5Igkn z{F0IOkLM;h3|R|kLH$7op{+)!=1V?I-C9DA70>?KUgii%xr6Ndv~v#`8mrPOb<6jp zvgsq(n95!dSqx`2n~8IXhPk6-L0!X*xF1=Ua8ArBG4<;Y$G+;)d*-LJeb=M`YaP9Y zpmQDovuLF+LW#D1$aXEF41vRMDFn?PWYOJl7~P1q&sKgPEb)i%C4S04Ec?a0bh22i zuarWyT8sZ2&GEruCF+BxgWgoRe5aTD@vj<5T?PJ0L;W)h#;iL@Ag3z=T2nE&d|+2L ze(`Y5C(v2 z-?pwxbvk$i`~2p+IaQbL9uX$wx)P za19tJ43~ouGB{2#pnnFf$L#R|SFl2_-Akk9jwe;4waJ zE5ud=MRU$!2?(zwKzZrk`yyMw`A8ycsLUjgzOAILF&#NxeRVr=<}3h%Al6z?bH2+dTf{nzj8{S!au4_8O zU-)t8G6O zUspN)2j=&mUNbtv0-2@yQb8dr$_nBA9^Q#wfnqI|VUg`Zrtu=VZm3(Syb&lpx74nA zu18v>ul)DF1#KAkfi)qEKAH*RUITgZSyFi?jIRhuyWZkBTwvy(=zFate$)EpEv|_C z&Yzc~QVy5^CcF%u<|BOsLA1lfPm7{^oL)XbGi?0E>r|dbI=dVt_-BKm0oiX!CsYMD_s1-%$5|<5ch2Gk3ffxV$lEqQ!19IJ?3iEQ>?K|3 zhzyknO6T0IZXB=kn`;DN$*2LPr7(HF>%`FsN!nzA<2+Y>+4+_IOl7UM`uwDf^4OG{ zp~1IRsN(-HcmfI0_1p)8dylCps?|c47JjrNPdkpw?D#6>f3luByryn?p=;;j#c9Qu z+jl)iFCVnL7N9_3p1`5G2llmOv!HCql%=_lq`XiUfTZt%6ZrqZ zsu~pv^M#RRw!`b8NzG}Va2^Au0(5qX*9i&7ylksD`m^~N%l3;Ah~%KcbQkgFyd(bG z2%-^!taRoBg){UPaOD8&K0m*L+mEke`S!tJy1N^jb5*(f^4O(@$8xhz%6EFMpJ5mJ zc9-^vJ_QU1N|phyxXSDWNAmCr@};vd=z%HsJX?hFxmz*^x2O<%BR1Gj8!O}T@OQF+ zHE;4&eo>Zcx>?S(c8|uRedjwFhg*96(2SoGHDX5PLDAWMgD9UY1INp_5Qmp1 zliHBTR`W9FO}bR_LO|1v;`h<^#Nfdl%q&4NRuMh;%HNp;CdU;hJZXi^!>gtf5N>`E zhNs6{6-voXc*54Ure=D@;??9QscTF>!id2?a~%5&ZB5N=4h@S}hab?jK=g}KAuCT4 zv?=0Y6M6`I8bx4J)IK33-(%!~^XZAoUGg8fsdbzCS`DL8pG!}CmQ){bG&l6V65=gSi3$Hw$2A#W=_N_rOY)Mbwk%@p6O zVn`NJc!ykT8|jX;v@F1d@vN$8-zT3A?KaGY?dtxdMbE-w|}l`>5=mrjo?D?6z;gg;*< zt2lJNL^{IQqv*xAE}cHdWQfGq=cxZ+5gLHW@td3OK`w32dUZ6?r6Q;NB!6G`cKo?_ zRW0S*?#v5S);G(?FMsMxbl7#E96@wqP%Q0?p=Y2CfHW+)39lxqL~E?YuPt^3HZFfV zE1r9_uUhYw#pkP&;}x?t4{Hpf54>nDz{J!LzR;QCy?(Mw1sSo&H>O;NUtr!rUD0p2TddcZI@XdWFjv$1CEB9$2 zNgr6&m52!c-nRNS^vUFP_xCe6Z~RHR`VvO&iy7BjpKK&W_LuhFv+L2=jC^@xKi?$j8eJfZtv+OqgB!*c z)%!Z^@s2M~UB7N7ofZy}Fz;YQZ#;AUQ1O6gsNiYk*n$$ji&i;2wBS z037cL7)!I$`!IKbGL<$S@LD^_0Q^y-d21uuv~&O&NBtwZP5w3GG!f{ zgeXZCQ5tm?vuX{z)PCp4JaWKlhJ(&Y`uE!T&hzyz-_r9zbt4!TWm7r5dQ*n^_iaKK z&lP1wC`(_700NA&!V+KF9$L-8>NBaL*_3Nx!zjjp#YD`pcB!8z-mO-Bcjf$m51si| z1q&8FzA>wUshX=pk1my|S2q{GP5SWql^11+mK_G8s?y2~naNIj@np4t)7vqvckc@3 z7Y%DD`AHeeSdM-=7VW~!JD035fWhUW>R>SlH}0A2_*2zCiq#t)m49Prsl=nF)Qd=r~;GvPu$*9i)6c#W%1z=&-34I2R z<(oOuO~eyT#boWS96r^#UwT`GUZ;Q2YF3$3=A7otq>nfn%Qc5cqe z{O(=P!l7$z@9PAIuB}>J5d9!k%#1!UNdvWq&K#5wRRz|GNP^Zb-OHMfdx1Bw7Kl3> zR5I-n;~99)7s4h4?K--`Y^_~Qodn;%0mC8xLc~C;Q;ssRJ`Sl#fr^kH_o>}N-1mAG z@UB7L*E`&^qPQ{DoLb84dMU=uErsb}q3U8v0Br@FzOA(c?_; zQFkg2n$9^@JrN2k44Xj_ZML};`)HEw#Od&WqsgaY!h^Qz-@FMOKOw5XXzc(WmuXk$7KYYL{w-)N9w;@FmBT z_n1I2YlS@+7vMWZSsHlees#|aHKi3&kLP*?J}_v5QMlaIt5=TRpFEWk_klO@&|y80 z%I`quB+wcVI24L|yItqDRRQ%pk?VDyrJ2JBpNF!>PvjA&7Lml{S1P1o{xQWnYZota zo}^lk<5t=Nm-mtN-0_RzDg`9_+S1F@&vHsDppElG{BE;_ug~6*)wza^*STmD4x7rS zv-vL}Uy7ore2X|tG@#NT>wdMLY$qzeJyMrds{Z|QqhN>d7RTyaY7yHReCo3iCFG9W zlc4YYC$WQ_tPOJYn?1qIA%f#Q*7k*e&A!u|vau@_`qR!P}bdq!bU~pRtIikwMP$ zB8Dt&)y3A%0cKN|E)e8y(?B`%p~PdOVzK}G?FabjRQK;9{$z)PpJazl2iS1~hkUA9 zE$13J-v)@M`7N;8<>=o+=4OIdyL32C!vf0bA?PeVHgUQE#r$-n1jih6UX}_$C#=Zt zWE#@1LQJy|6d4*cWaP+M-9(}eKk~^4CVZ|k@aAqTPrExblrU?gu2OZp9xx) z>*@2K(@Z3r-7ft!o2ozfLve8G4_tX&lX*&6;{%7!W`mU)*EwPk+95%j{=rWpn5xZR z9Y2^48vzwyR=}w9LXuC31!et$4wMY135axjFct9*(J=ZuW#iprK#BG=HbB5 zY^Pl8rFlfblrMcB61E1U`}8MLi$Z@@EA9}1KVb$+Bz$(pX;6ENDcGQLuo*}1?) zg7;Yc(-LoGgTAc{irkyNH142}7_S!r%HHq988k|+GT_9yCXW4un4-y=ust`R7i|nY z@@f;ARMur@LvE}5zR9a^wwhLWgW;}Ac<`L$`EfvK8+M*&orV%04ihY7@J;`*!o_I4 ze5M#`yz(QV^X7$*eIoX^Jr3*!Mc~;1ZEtcAnXJ5jm{Jb2DOyxsVljS_0t-^u3ndzy zmyqhbc{=%}M{XLWT-g4kxk2jFA88ZQL|{s+fngI#%NXqOB@{j?@0M;I@qRiiUh~ql z?{MQUN`>NN%c@d-vo}}Hy~D(4De@sg>Zy_xPuHg#D-yh{?D;dt*#E7bQBN`V?@|Ce z^LiMx{ey4q29zsfv#&XsOzma8YRr;)V&LZ1sy#FGNLmNR?7jJ z^Fqm)6eq8zUnPcn&A6@WJ0jTGhHpYmFB0 z*1DXQw2r^3dbW;h;yCZ*E(VXF$c5U{0IKuYW)B;JvI5NZaxpIG8Yd zJh8N?-8LyIN}}!-BPYw*!x&mJXof~df&EbS@!Ly_vyHfz%-JU}?#{YF8Ev6V+tBg( zTQG;5VD%lft%s_hr(O#9q+yWEO0w8Xrj@Yb&k<{_Q(A?Hl{n7ZN;4YIkT2g}&wePL zQhc#Xr{BuZ!P__H64+)30sY12nZrJ*qV&hzEl6SdBC4(s*~`s4f2%z|(7iG!erf<4QhLTKJ1#^*_2V?7 zV}T-RSo%v;qc7X%%dm9xj2WG6nqmv?(!@|kr5ib0+r9nV&qq7G%qtU8lu!_nwCL63 z%oP4eDR2HEO)C$jiYP%+k7w^Ok@dm+4bd{a4VkD0Z=8o>>Xi_;nl8pZx3hU-qcoHo zTAf#>=x@q3bJxVU<7*g{yh-Qs2DV~DAAoV;=|c1#I{Rd+XOP78yDjdXub6$Y^3xW9}$g)?{N0;Y=fERQ0U2^I%r6 z2g6vgPDG6retHFgby()Wk5YN_Z~xk7YDL{otm(@|@FVN_1Es+WB94j2@Ee|_`jFGn zp?Ar(Py9mdmMhM$I83g|oLqF)O}*~y^aG?cPkkc9Ygj+b!(cFEefPb1KM1||1UoIe zZZ_Wu1m-fa%LK6uy}-C@a4n&cd8IPGAhGszdhi`~M%kB+`FBX~3xk;-8VfipIB^6! zFON+vu(>iDq?}J_Ir57`KJxKm&V!#4ZE{CAIs~0Vb`Qlo(5|iI{7~#e<9{lIg4K|n0qp6e%(By zovlxSb`60GN7eA7KskVV9NKXc`ofzfF5bXLET|uTK0?~*>n+8U9-6t|b(p$ei;ahYkrQ6e8B*Wp}L&!Bd_(}l9l0M?B zOe;rtz(na|VR{SAsO0+cHs4D>`hH3_-BHTLK@5i1X@R|B(1EbmRz-`(Jiu^=O*j~`jE9~4PQK4ET;A;N&Jyb1LQ0ucoo+a}MOtX2Dw=NWU!}CWv%Q$6okRR{9aU2xi44RMUbKo(v~LQp zPx+kmxzM9sU1Aelu?W@LafZbW=}SSoMaz;|5c~+DS0QE?xznrss|g6D<8 zk8r}}8xP~4Ypawu9bF&Q5@oO5l8@>tD?{d$m3&#c=FW&Y5B61nH46Sqr}5Al3|uHz zTRx3!|FG2VR@LcjB>suG>)KkTdUiXNG#Pt+iyit&jkq1%dL2y`>V4*rM3wR1J7;&X zy>8-T@lo{Zt(l=rp7v=LC)etHpF7u9nD0dtAY3rj#rT&LJwzuf%&mo+EEh-R*S>1$ zzGB*PTqVMQ@5=qLzgowqm}b`(aqk{0GctdF^fz|rVzUVGSqP0z+Bq;aw}P0h!Rn_8g4#E**gYpF3ybUe(OOeKFJBPx*2iU}(A{04*(ku%9Vw;vC6UVara_?h zVDb1(p^FE7?qdhWgcuIt~L`_oU1|7xu@;y+c9Ay4i8@au9AvpM#epSu5buS zoKeQ1Qp*XLIoR@avf}q7=oO+N%H>J!Lthn5wj9WFu9?$PSWFs+Q?PQeMbPB@`*g;> z!ubTAvXcCa`_l5Vt8ZeMK-L0pAXN@ZE5Kfu%|w$M`2}HhR(-QMYl~Lzw!CiN-1>fW z%rYh-&ZOGp_l?k+D+SzHHm|&?O5pMU40tENu)}nAyJb$+uExCWF3{Ib5b8V(FF;7yRzGZ#}goh^Zq?hYWTRI8TP-%tA27gKDqUkM8z2`Kg=N zOI&t~*FEi1(bn4kd)=q_r2ak3%s0NH>!~W0>1J#HX zp#4Dq+G$^ZJpX1yrYKg3kWaA>)uVyX*lkgSJ^?@e#Ydq_@A3TeEUoKVd#hC$Q?Is8 zid4G0#;WBw#voo56QoF#Wix^%sl0cwuDFkIUJS((RBQYEf?e}>sYY{Q=49d77mNx) zw)#P4(3YgD+Q(75zk%WR();187~kQz6GCJd>G#SL*iouT?gRMT;XAcKPtI>26lGl+ zzE2~6d{oU0?7ncu?P#}WqQj4WJzfHc&I%x{dE0ibul)!qEQ-X@-|Q!1;Cix%&Q>@d#seng3Do=ub4%q&E~;i#s(uTU^Vi6M z!-Ax1ci)*tznn!Y8I~zG^GepOAy2{WvlKmWa$?v5h81v`;Wb;;r19l*Lon}4Ipf{f z^EDq2E6c181gm6<#R?HE{FlG=$Y;^rU_ks=f7m#-YrA`~YQLMKJmD?#ORlyRb3yF9 zT6kqvKy6!3+$Zyb2JuH`0@UcRNkoGRkc{@CT4gWq6Lgp7h6OgSR`4IWQ|;tv^g}K3 z8&*}aG47;98HXW5$`ARYjQztor$~aBWx_K`Z;oPq#;bj|W*AF+9AT9E(1Y>WKN++C zdXYgf7{{hKX2oa)aR#tY=)8HFcSLGhdHThS4eMP$ zN!fdx$Y$5M2_9I@NmVuYTar(J!GlPOn#CWQ&3>q+NjLi%9T>yuL+EfZRosV!p!1+- zFz3mZK2oRqkp`CTOaz_35R~Kr?!ALge?Me$`YpyCCBmD)fNJ8O( ze0tazTmY|ui9oO$xGcwJeAA&0o)ZLO2%Rn&P0bb|*Ck-9jZ2+<;*Lc}7Ch!+u|Yl4 z_rttz%-;Sbd=wI4&c5UGM^vGwy zkqnI9I7nqLnjBH`OM%q<-2Fo1G(Y_8hL3XPoO?~|WNfeCi!k$7Y`61Hf3bH%gFz)r z3Q@(*3w;R11DKwcPv-{PNL!zpPrSp=f_TwAF3^SbK;YiAJ6R#&fK5g;eMh; zbzK89m0O0UnFs?&lL0{Vr|aU!uo`t$!?QS(c!~MYPaW5Wsm$ywyqk(u_q{wgCqAF$ z{3LP~jIiP|gwZ@@FBjsP3K5`pf!&K#X77*hDbS(~1?gXTrSENbi?V!t_By41_2nP^ zi1mdbGnhuB`lWU0cR7}bw*XP|n(f^Kvi!heBi~U^lRck%Pm7wqEdw<>oAO<@J#Oxp zX6(Xn;NHcKUu+UOuuYS1!xxO+Qcc0DvA9@ApE2YM>l0P#qW{@8@cDSLhC1gHH}hoI zqu+YW(qOF7T&y#Cim!pa&IR_<>aZoZE$ee8Mmq|`IT z(aJw`>CfojMWk)27%)(lP@+S^R;Yp(@!mT8#t#L~y_e-1X&b*)?xlYJj=h{uZ#Ig4 zW-T42h^uuk^&86CxKD&95~!jWUboSJ)5J}{J$2)Rg%#^*k78>VTfAVHA5dVFGIMKjqce43tivpt zqM5zA`Zg%T?P$}r)W}MNl8|1&TD7NgTv2YwdvjA1P2I%1xT3U}& zq)OnFj(6!j3bsGI9#jABk`DW;izWF)MoiD4B``wMnEHxS4f7jweiffSpLJCKZmOz# zvh{(g?s`1HyxY-X1|#iqp$f#s9t_9#!nN#WPUT_S`W&resTTtHJ|`ZN9kqC@oP_oyc0c?gfn~|U2Hm%rL$R*Q~;0$cW}0D(p$OSM$ubszHMzaL3ws*R?5+%eB@h*v0bJbh!-^$8{Eq_r@b zX;nu0p$-Aw0wdnSkQI8Tsr+**g_xLj!MAuiZ_trnPl(HgE!xjk!ZH3#2YBj2T*tveFJ&Aa)ky^`^4nFlsVJ8-pYg-u$O z_9pfGS^%pqJ7PwD47xS>%-x>HCC>Q8ixt95*K55bKbxbWalD>#&MD&666=*DQmxhhN#kP2?ssI$TV6}t6Bd|%yCH2A^LB^&C=J*vV5qmckK|7L@; z2=QxTR#9o`tqblhps#PRy!@@8@JaXu#mbbZ5y4?&M@Vv_Q`w&I1qfEUagNgO-Iq z7ARVu)YML5!F$y?Z#wjUYs;3%{Ce8uKhg*gbe{@Y&wY=E1T-s@J4G-^bldMGUA3UJ z?8O|n-%^5Y-nN1N-mHisC|)>y9^6+r{Lm-Fh>1gC(e$I1z{0_hMkateH4QsOAH&px zMn$SVac-%2FK%|1V60+V8~}~2Tg;eJ2)Uio&B3oeH1^%R`gHof$w?*#)*TKa6bOKj zDAz-;L@k-&50;ed`od1P#ak3sjpcG}vOnK&4fI%wRQ$o3b!jwv)z)bhE!I{w@uX#T zx6K)Aq2FR&)R4m#J1dzrkCo#M|2%Lhv6wkAnN5J^OCLuO?T2STL9T+xVM~ib;9flS zpP&C_^X3oG+BdwnpfRqx^CQHoRQnul;mE`%Dg4v!7Z+ad#O-JdIVQYI?WvZ|kgonB z{SK7ZGr%A4W8|i=Yh#4yGO59suP(JpWKNWGXhxh`#@4Zm{^I}!;wZguJgg5+w1hK( z?dcjM`+IXV69L8d@-;TTef#TAB0hf$8}vTaC{C0DB`RrJdML}%V*FygP&YKjQ1#k# zsooRn#I=;L)aU*6Z4(uFZ-wvtFc;GZ>f^$nP@rY|+=<1`D^-k`iM!k@ON5knKtkA= zhdWSYUH!dJ6NvlVx@W=aKq4g&Clp?{I2-CY4PaZO z=H~nJx3z8CD2%tU3yu~K7H+lQdb!qzk5-7Ww_H@!VFf+bqTqCILy54j&y=PKe!xBw zNhf05mykzg_6piXB8ly(rGCHtCeb4Igm@WsYF{!x5?749yJIaKIttvU9Jq>Ks6P2F z&I!oP_)+=o#Sq+71tK$aE{reE?pDG^MZ4=HmQQ`8hkRQTqU3KVV{O!^Nd|a;1qRN2 zK{Wx#%&>_=6o|D23;QxZnDC6ZYJRvfOCK1^f3zvautO`l-_z)=qt24(cGncWSBy?a|o&7Y|L1Gp|-<=)^UgScRX92SDBrxPK*b1PJfwz1}d^AoSeu}JxK*lo}%lqTgofd zS*sd*=@nawtFMpVv`jO2WTMc=H8XqrVJGI@cGM!#*MTa4SzeEK=x)yJzDPWs+mU&V zWLoMbQ0KU!Ad%RZW|7=-orkxNG{IyK<09ZIRqk7O>0ljQpSA48DHNpNy8t0+Zbume7A*d}*_XAf-@e2y zcKNzDCtPE`v)HpU6osPWbqKZuejom0vGLgt#t8fjECPls!vB7pa(KGIvn#k?t{`&4%RW_2nY^dy8poUNdjvn- z^~65FQ~uSHIZs0@EKT{{fpk&ocyluaiO&bnI0_Wka3~mC>uGn~b`*?@YIh{Z+zO>(CzKC4`JG!JB4a6@$Gs$!G!!g8xAtqT~q3bW?`VPI?ZxTaht)O8N zH>BJ6s%bA4!1N7ZYNiSq>L(2O$=`y8ucc8=Wg=ak$9F=B{&2iUu>#S(_X2(9Cx)60 z|Li7U5&MwCBTR+bO$ia9E`ne|iUI)@#waAl)k)^Jk|9q}vzElDh8x{sa-FUX{s9yx zT)81$Y#%mZ?4hA!1{Shv?Fo%ut_Z<`_MCJoYPthfe+ovd{x9A8W&a}<(cO2?RS3_{ zE6r}YHWf*>pSCO6ZFHlX?MwWH@FM8FMt4F16YnqN`A-D37QKp~a{!piqCE=3mBQ;y zk)(tUELZUo`dE-xceO`WKyvx%mhW{){qMK0EKix|S+9S&#(3T=<8fjZLhB5;c2IVYJdFc@rhJY5ftSdL!z_#%EHp>W;a;dInt~H2l&4z*6C^%NVd7 zQz^9JBBl{VSR@JUrPjV*8~Jn2i~{TKtc@P2>$p=RU!csq*xJ1D7osBZ+z{eW7q0m& z@3PbR3hALEmy(XGo#Zw-5}5*6I-R-DT8TPrT<`Fl%=N}3Dyo_+bLmad4Id6f$?L}* z$Hs2-O#n(JP(E~kQMpVRL-f4asbQxPVC z=$u0}RJ2DCw>uWAtfB#@lYX)Z+1oC8@y?ZJVL7W$$nPE*=j;X#=nP;nW0BlT^6rxb zuiD#mn=vrW@4X3ceVx{_Ec;VkU)QES%P8p>zHFq=o^Gmddp|Nb;P5w?wAWz1T0_B1 zN8X@bYi}F*h*(k}KVN=%%21N{y_WW_wu@0|^>s9VGC_(_9m;DZnx}78eJjkw#_l0( z!UFUziG%PEggd1ldJ&aW8)QdTlqvE|_Hv8v?%a!gu86nC)EN@};?1t=XnvvkgLea< z=Tr<^T|zdBg^b-^w$JAe$*&>iWS^S}G?3SvC>JPjJon?Rk@zn9w`Anb)&$VsUuG>a zRiKupeZO^E)vNUXV(rbtq5j{#;SsWvExQrQPGl_^l3n&K$`G<;%UU#pLiQ+xEM-qj zwp3`yR!R14lwH=z3>w2MJ@3BP@4Bz+x}W>HkK=jn`wz=84xi8av%Jscb-vEmDR}98 zpjQ8y<ge^qD79GgS9UQDjhItXYeg#joG(n5+VN`ma z5H=t-Q(Jqq(g9tv$R+zyjQQoUYAXnH8PLIpK*0D5O<}tyHgC5%eXGM* z_6zNv-3c*x*w6n%PMkK@_!DXg4E$OKdwhmS0pFFCaAhAOn#eGs5l_C3$knpkEk{o` zn=3Xx2NJRisu7P`vpG~$`-zuD@7?&MEz6sQ;6(4pJV2IW?j{9uyA-NFuTpf`oj}*SFqzHRBiWOCnc>lb;lc&SZReaaY z#%+1E(ptbq?r0XQ(TpdTYMHB1YMAM6Tl59}*|O!7RY>PjoGP+35(}(3Lm=XdDD#;2C_qgtX`+() z3qA}>yqg3XM_3j0i}T+&?k=#0h}1AtF3ui`QQ84yFyCrP=DE5$3Z{Xewh=t?f-t6a z*-1g!g4FqDHSwUkV%|d(O%9%rUrvshuHV=+HJ1*nP zIyU_KNA$;n!IBhhsi>of+RAHs(H@cftb{3tM)_g$Qk=QX3r*Xu+55f7GQMpAu+odv zZm6KnN#9)P8;4ftM@y*7r3OD3=ERpUmzgqqHnpM^Km1E$ggf0&t+A3P)pN*ivX$|7 zurBwSn!PLS7M5`@)~`S}0!Ze_uAvf~IY;~)^eyr}27Azp4Y0bS?kdm{;&W~PYB)#<1a2e^U_f6ge%$QVu z=R_&S(sF+-1@1!LBW<;CPc%CD*EN&dSP7a~rKpvl)fipYrEu%SxA7V#QSuE9v+y3GzRBuHm>hJoi;pbK1dNw|$z{QHN~!i) zaT)Z$&HS4i&+Zs191>=+@SMLLf@F~*k`$3cztEVH+Ai~wY%o=xdKSK=z88fw1VAye z*-+``!o5#*xG4HDEz||Jayy{iSxKKOUaRA~w5n4>X+Q>)U~CEd#Cn7> zbzD^PagXyWai{H5jA9LA3ai>(9=9rT=Ty$}uU0&!9n8YLJv6MCmWfc{lTN$$-6kZZ z(5LKJPUiz!O!h0=2xdh(Rh!X!va#m2%9!%2g}zZ1ePO{S(GPs!K#< zon!ZO8F;KQkF1W3U`>^ujrq_-+t~bGu(3LZ*Dcd_`}@=6Us6n~nrdo}HBVNoXsiFl z!IW9o8dSA=cvhNb|DS12|953shlhyb{bVj@5|z0LKh%Q|n9{bEdO#Lf2++YCxU@IM zYyDx(RbLQhbp^V71JP7aA zfsVDvUDR-CzzaH4>S=0B-w+N??qT!-)8Zp9XUj-P$}t!TnZmvzaY0BjFW(|C5*~zH zyebSzp0%WjF&f*xrKVuPi1DcS;hSwvn6fc^ce7P4L^5>SW+XNpmPT{dXnyO602S-5vZ>m!yG@i zsB92Qu6H}0uNNR<*BXyuFf2jzKA~^5yl=F=e|$!lw(gZe=F-EO6*t6Fu%$pQZxvv< zIQ6j(N@Z#wY0VjF^dd8+P9mTspWmMN@VIPqFI8DGJ#lxA;Baz8_@ongBgf?n24PPG zR-AqvY|KD#6lL^HmY^AKD$_;!k{&j=aceP~&aacX%Rv0p7OLV>JG-mD$=&uYXE`pv z!ej3<7rC35^?_2UqfSgBWWzDb{esKH%q7~WjQ(Cn!Q84`KEaHl5VoERIhq-~yAlcF zX#t54PKJR`a!?EwO%~X}nNfLRGZn;5T4zNGVL8pNdJ zv<^{wYFCM*OzkZ`PwWEArNxzOODxU$z^}f?zr)pc@UWZ9oO~QAMR?~R%il|IF)NYB z(&s$B5ZZra_7^GzO-w|oBy7zBi-?C2F{vy{zt`}G=QvquNoYEZV%X>Q($lK3@fH49 zww3V4v6Zqb5&;Z80}uF16%qT0QE0PeSI{(?5Ht1RVvN_g_;CB_MLDRt!>`#8VRc;T z>d*Wy2CV*j7aFStP`ei>O-MgfGVIMfR%=-a6S?%pW1`(2F?PDD@MlHB6a9haru6r> zTI1|3bZlj^`sYz^f#Z5Lk8>)?n8(?n9-f`vis=4W!lr#OEKrq$RZrtt%+~;yfkQqf zwK)||EB=5kah2^DAg|U5dfrtNWtZ!8Qe;heSjRrz9ZgGED!&$4nI)fAiZ$#?$$CR5 zuX4YZw`k=DkQ6eZuoRRti33X`u1E~N#YNPo@LqCsM5;ew_USxhJJC6uau*X-C zIfwO9JusYZCdT2j_B?A8=_4lJ$}&fxVl~TSnnA^aVam6^0%`9b?fG4Pq@)Z*oaoFj zv^Np`_~;t-G(rN|C5Z$NFmZD5E|oE<=aHCJ5WC;mxwo|MGD3Wa`xt(b<66eM7hAGH2OJr`Q_9mW(89ZNGm$`T8CM9!cqG)F&e<^ePE{!KaTZ{1ubq9CGMH__T+ z+BZTlg64@`@paMbCs|&cm%Sce_dtb3{QP)v z4xjVW{iV`>GWO#zb340(E%Q=({+-b@YfN@5-;s67#Tuguh!jGazlwgp%)Rt@w9S6i zE!DCAM8`KS&Ji(r+cHqqM62vC%h&?&^V75cFU$gA&c(f#v-K3+8`mAzeYW#EkVpRc08>u zDzJFG=ghE?c$pZDgC^EmgEj?{k|J}j8VE^rZ5TjKpBXVr11%4j4^IM|!+D${g&KUO zn>VFjUkmIlmwsC{D*D995eCMGobo0=t)dJ%A5fn-ZdD~CSDeSuTi-y0Z_t7;M0T;j zR(fH-{HD@!6-|*%H(moMg{`UeYx^iQydtb7)^+YFDM0x)}vK&qS06qL5%E^{C z2Zqg(P7b2HCAJ6`Z5=<~U(gQYY_@m*iP;J%*C;%HLvFzH%G*utPhOvw#lol+XfHUX zbUA83L)3sE`jTF>+HMj+3wBjk-#Bv#tZQ}NZYLo0#Mg%R_v}&6mmRTz>{RbS(59xL4T9!^qzyYc(m+q3Ne^nk~D=$E>KdqxgVcPon7DeegDw|szI_O;=Dg==uxs$?Z-ESJP zHwwhV%j|JMmf`#xxP%#TAeXpnr+Pc zV;?od1ij2&+uT7sp%LK$R}IABWX{UM6$`;HAtrqx7F|3~dm)Bh(Ke7LKd+4`>oQ)H zAt794o>o0F?!@Y{mS2=vfqp52Ma05TFyg78|Fr2!lF|S3rbGU3H=V`~z|B9^lxH4= z0N*y^e;oozbA&BgAs2Z^qy9kh{@G0&N9+)?E-F>=bZb*wJxbEa-LgaSfw)qj$--U7zcmj!IOPEuzQA^AnedK? zt{SNLG4J%DWU-B<`l}<8f3SU3Tb2+T^ck0GGXHVLii{tmOK!+Z-4#gjdE9jo*SQ12 z#8}wFq@gOJzQrC#(0*#+i!vP3(bVs88kj7F9?3M1v)W0UECEe*0q}=97e`k1_6Ph| zhxZSSenbbHiitOBcuUe_NK5lb6MP!?n*ngE2`ZokIKTzUMKoGMpisU#$~bausPOsl z?;(1T6@wp5AL5lccnV)UIfjhG(Z<335EE$JWmFOZLS#cl?GeHz)F>`JN0OJ0F2bye zWtQ7oAN(qov`=Sz+W)rqlTataeRu-2siYq+fcT0Qin)OA@9e5*9}d*N;>SL6$4ilG zb9$d)F!dMhZnA^AwgNJGp6$aau$K8)Rd|XhO`8jr58jkW{Sv;SeNNhoY;@@YXUZ*( zy@o`eK(~y|nftx6wPoySX)5K>oHmXdtz=$Df?g)Uky5rcNtW_+$3*4{bymo%lGOC4(jA0qA24kmQDBniP3C4H;J>78wONLpQG z2@P9#Fu)mVGaoD;cTBfD-hR43THII4E1ieyYrkK$h{;6=?Jh6H4}{iGGXI`c<8jvJ`FkJ7_eBUk& zV998rdP^Qan=H4-@MBbFMdVFrl5>5FMmO##;gxfK@ww;_^*n*Saue;27iXUG=r#SS zTrdvr-+=?YYC_jLN+FoqvxuAT%qzKxFUceiu$V}|+MC9n(A)`AJ}}l_`S^owREhsM zIu||Tzx=C#Bpnkpg3yXGGHRKqZf?U$ ziA;iNKRL}+0Z`mN!HB&|WKHl15cNszMH2-W{y_LqNl9eBz2zzj7vT7T=yqubcp|w8 z1U<4V2yvEt74#T#BsdRscO3sX+k@$?e|m7%FVf~S*LQUmS+zzPG1^!sX6LwlI9W6G ze;ind0~{Dw&m>OjXZXHyR4bTF(Bfl8Lz2e<1i2>TMlyvJh_m0XiVv zmJl#juL##~#1AGVdK4QT-`dV|JlSyWBt+@y$%wAmw76bGZ6;A^t>5zKG*t*Z>)v_b zxE)s=^6-LSMN-(7vX~naua?Gsksa|2dJ|GDabFvBcl}P9G>GI}eL}~7T8~f}OHD^E zcEcMr3IV0;qtViK;Orj=^!5HX*jfToR#a_7Ax2~QUUYYa{2+T??WS{#`5#DBzjwGi zqsyD6hV<#qA>H$iM=D|O!36G-N%Xh1r5DKFX?!Ln?fn7uS)lK!$~&L=RWZ$1)K`T$x)TL}nC9nuSXZrQshz@z7+bKTF1mowVV0pr2554RP|<5hUeJhET1 zgiZ~Iz~i19|3o(eb&@1<*?}JD7T9@+-d|QT*6_f=sm#eFE-*JFxjke7#Y&bNSG#^` zzNg@3*`3?rS8cpadOLV7clS(v@fiK=PeAShmDUEyIYq2!^PL<(~5?0VJcGhuH0rql;g69xj^SK9}cy zz=?+&Q9r_7mt+F#wv)`|Q*di^{R8(%T0p&jDnC+8T_B*YI|dagd%HBz*p|zfQ96_I zBIYC!iYp_85+i+a1B=JA*ppvO*JG0hK2e;nt_Brc7246xFp(-yJTufaA6tUcy}~Jv z{au2&-*=#)AHl!qETgD; z+__}f@gaKQ2@+?4=EAhJ^nVQC`t(RQ&*G~5P4k1RVqsnq4Ga=3G3W3gClkS4=uy8t zM;)4zpJ+dQugmPZeMXa8|8PF@btPf8riF0KUFg6L*s@)K)DeW`TG9%?Zrgpg$Io!R zzFGf-+l8RSRnxV~;E)Y?1-dDZ%vZHomGYZ0MvG(>Z5D60^4w$oh9*1%fWVf9jp)j;N`f_~s3R}yy9mPbDmO0<;UCQ@TK7()s zxxB$&$wO!z8fV5a&Aspkf*oSW^dvUa_zQ;M>UtkIplA44j^o3HF5D^1YKkulRE~2U z0%wy4XLF6Ar=%gh{ex2HSz6K&B%8wPWBYGX?cSR#>;_KPo~RW((G8UuT6FylI9b4? z_gD47jwmDOef_BkIL;M*s3Hd?2{pqh{C;<{XUGpj;`FMQw3l8jr`huuwbGs`Y7VPn zwTa8P=RJJfD*d8dZ!Y$u2~|dSP3RvR}!|-wP`UvhD|}iVB~2Y&|NMnbtLGu2BV0pP(RGg}z4pD@EK6*M$z$ zKL+>rfb1|9MhZ}(p8S@)is=@X?Z5P1{<0sBH{Z*1aqBucn^f>--baK@D5HBUQ~7RV zzx^Hwdm3<`{riuO5q@3@-<{#*9Ta6>PW=xAJ{G4-bB6#ijZ4)7$(2hT8v98d{-C-2hA`(*{44~XhHQ}{^Xc~#pC0?w4kexT3H4!8 z;Q`~vQmz&qxoxT_uqG-ntqZ18$Zbus1)X2serT;kcm1x}(+}B)2pF z(uqed#*Ky2Q`y0nJ{da9Fb<#x(;xOAMI+u!5*$XZI!ooTGB)(R`!;SJtF;#AQZUz6 zPb_n8$7`en)vH~xwa%|sQc_$|`F_ou;%ao!X@R{H!mDcq`TEkdla~TUy09btpEir! zBoWl{9itT=s!sRSE#6+E2^3P9o5_qktS=L>+|h+dqt@zMwUw9PoM5Wzk#{;-Bq|K+ zVbGRH{kUusMS@p{zvguLffZm@Ug*%E^N2=W$mX5NA5sF@;iQ`k?CZ6&p& zaWj~9Ripkhjj}0?P}yxcqGemdL|E-?I@Gl#n(b1bZ-A15=PLGB!1rEN^Qg}m2A-0^ zAz6KLq{+WSVSSOT)G27wulx9>?oSG<==9R@S6!bQ#{)9+LTNUNvT2+5J-dWC;ju8B z3vxzqxf9NH3+Xox@ol=cQLL0Buhtf=nU;N~J~{P{y&ZC3#dl?IGCbKxMKEWY0}vb* zija+Z6c7&i3PI_&vd%@>c5ksaES>4Z5K>kQ{D+_$w@;-fa{O|z?sGuDndgkU-;JQh zwd|zCn1rzBm-`STirS%UMqMcR60ZzB$tMGAh4}%EGoX z>&!hyIlZhPyGMG0>AaJw3r&Kz>|kYmh=d9>f{ICnor+v-z~TjU(M*hJ~X{WwWM8yQPSTB?fD(9`Jl@FKs;g@0N7DR z?fantvR*$3l~@kI^mOF2V&J?b*jxTU-0wO5ftY!t_HUJxm*8aoK(x}0sD^C*)NVl( zHFwDN#JR0;1pOb#=V)RyI9_BDa(~U=HNdqHr^-Qu!#RLC(2q=lfe-B%j(9nUq<;l( zTT4~s93+3{QGWOiMf3aAkvMW-p3+Qhp+=!k%7;rMvB|aosG^Quo4nC!< z_30Q7t7c{{=x2n$-vOq$6gdZ-03*;$!S?I>-dYk*@pV0o@d5)|%9b6fL`gw(8_F{? zIObG}ea0>llQDOywUVxXD()7;eiBKAIs|56xdti3hc`qMw( zm{!I7(1S=5D|a{sE)>)Wjnq)duJRLTFUxqr`z}#tCRwk3lSq!TM|I!5xiGc$jLmV0 zpCd}x=}d2!|#t zp%bCMeX0|lN;9(*eGU1MyxCuKaZr*Tt{@J?zUhrS?d|!Q0ZEb_a0?h9b_TbV9fT_M zuLz#V5r4#0&byG2bE>)~?&u;2T=u{@6Qj_CGkDC#oaDQLgT?v^MXdLgRce3Rjw=R* z`&4q*XDT^QO7tdpA$=nWq6|dTf>ziM3n2h$AtV}KkxEJ=}+_Xm%8*d?O-gI~C{I3O{hq@`{7a=-G@Y zK!>ET10+R1Xu}Rx>J?cqBKw}$%S~tA@gxPE{NS$=HkJyf-`TPzJ;ORkDY>G+>WlwC zo+Oc#0lZiQD!W$>cuD$T`~D)H3s{21B6x{-FTvD##?6hYYdShN8zz+*&5sm5B~W9* zAZ%c39(%(8IFTqtEe?lLG``!ei*R6PohQTQ*?sa5rzX8lT%FcC-LuR2DC=@X{%fs8 z!>Ibut0M>~7$RAV?71B({s%HZyO;Un z7w4DD#U|iN00k>PDyo(MdZ36&-L8ll+@4Fx&Ae)p{L{@jM^}HelJ`YUJ^2Hvx4usr zAq{=~0$LGZqC}R(gFaN!`=r>IX!ihAR4BX!NcXMuJQ||AFBT6wZs?js=ZFXE18Upx z&RM6JFTYKmV5IO4Ky9T@+aeSW(du{y&hd1495`alq}7U3ZWx zpI#xi14oei?lqg2TVao4LmaxkDR0G=d4~X7rf&igj^BKpm#B3j!4N+tCd#A&$dYF3o~fj zeS9QM%d>fd`5C*ixdBx^SoUKz!~w1$NR{RAw%|VLZW7ARLx_Gyc28EhKUvYaj&Ly0qSl#y(y!E(;26s zL;*0~0_hI2?C}W{a>D@ej+mFp=I8VWQeu`#IXQ6%xE6E(@^}manRm6WAa6~fgyJ>G zsm^(nJKvG(Z>nj@%VBJ`E+25e3M0d7HUlJcBer||_)M9UQSQ>v3Y~*1V=b4GuEr^0 zloR~Fes9e5P~VkK6-cHe>Mx5Q}G{c7=vAZS2xC%Dtzx6{E`BH*}$|;5cI>hJyAE!iXy+Ae7#nAYNFK zY`AJZbfBOXe9ZY8cc=Xmjwa*i$Bg`4sI3F%VO2}ctLYj&>cMlJkM%WE!nmXheoPvd zFr<%sdie#J5GzrEEaK{HLM-mJP~lHpCCny#GhN@ z?VnYv#mXB*hBnK*&7QzO)nXS^<7m$cd*XY|bfc9(ukcpae3Miclr!cwer~N{IKaF% z@abM)z0hj>oS5-(lX5vh_LV7|2Ohe_k@`V_tUra2i#l4k2E@@i9$S3~*RoXC<&nGx zi{z3ufYPoQ3~8ah&l8oBdQp7PWKjr_NqEjRHf#WOVt@<3<||qA}pHg`mpN9+`(1fZ`97pze!4;&%L|wja42`UfKQkLMr!ch7%w;|vl5%;`9C z#cdM3t^D#2WaHLvYTDSn8J%?a+PP`~5qH%gr#I${=Bt|d1KEZCfjFaw?8rBXm=5j8B{W%D4MsJoM1EW+&V%sdZ&wtqbR2zn zrbHUL?m{U>@9q47Jo*DU8efDyQKz55rlTv6#Se)icrXlRqsRTPC@{e8q|5D~zbgO*NhW zSVDqql^i*Gz=5bX%|1^9rAYhU(s66F6+T$k2(CD5*{sV!W^9RR1$?Gs$j>fBTQHG9 z1UwbfZU6Ooq8LMpcn9$mUZQL8jMrGZHT|%~7Vlh1AWQ+@p%d!}tYU2V)_~=GG;7Q` zZr*rO&y?*K8ZnKT=0n1<^Cwt5@1Br5jPxj$JE{j)KqQKiu2Krgy!hiI8v2W99@%Rp zzxr!j%>0so(JUV1CmcuTuvPJd3Gy59(z{HDn9pWilCMj7`21Lv1W%5xBMJj32x9#y zL8>LQ&>m6lOIJR$Eea(|3M{DShto>&?+_}FZlHPO4!o(1918+1nHbxCDA!8n*{JC5 z;Ei=~OZ-}e@-LoioDdSxm6&`yZBA(h>#8T-9_pFLS)fuKrXC7yI}2@ca+A5Qp1&f* zVd!9GN03ON-T)eXU${Y&7jDL}w0*pvYJAOcAKWwgyLfXQ;8o+p$tJ)TyS&1<_zZJg z4HtU20PhU^-0uz_js@We(X!?y8<0J~*LF}q<+DToOK4_9WHgG93lJ+k;s z4HXqU9ivsL@{H3vAPqUChT8Wg{`dotwC#fuXvbmuMO)-mGT0{KbA_N%88Lq(^Eqis@%J4uT=!Z(lC{k9|`Xa5xJl;cL4P8G1dg`NfIVK{8Rle%^Ekl2Px zU2MhD;Kpo;ws^}csaJ8-7Dkf=kBp~2E6Gz&vemnL#ZP?dR4MoaIse8!O!+PAtFw9n zbfRaC%spC=x@eq95K(59tp3=X@&xUJHEvCk^lZV+wS*m>4^^HO$r)%Rw>?g11unW) zGyUzf@h}Fai3~(fdn_q!o8VP}O0cgQoMrPB_PKxbvDE6R$E;ntcJGsslVTs=XR0W- z-ZsKbAYVxJE5*&T!F=Wu&zF3JrJldNd0xFTR%Pwh>rc=6RTZ@|oMzdCP+}=mu^}&+Cdkyl!0>Co0;0t8JDoH5g6-W*Q;3h}H#U8Vy) zZSBD@2eYp^a;@(l2Oj1YQA@KK+Eo>MKE=9MN>7)?0BR~N5)VqtS|*@eajgW*-}5GO zX;7_aa0Ar#dgOkkGgThobpOl0Si1R;!GFaca1aMz5a3y!hXX9B2AmfW zPLSCf(c}98hErFE>s|Y(AEN@XiZ{BV6a$L9673n%`o(J@QJP'mDE`T-%4r4%tX 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 From 159a1930c123c4396191f781578bd388d9cec625 Mon Sep 17 00:00:00 2001 From: Brett Slatkin Date: Tue, 10 Dec 2024 14:56:09 -0800 Subject: [PATCH 08/12] Removing 2ed code --- example_code/item_01.py | 52 -- example_code/item_03.py | 199 -------- example_code/item_04.py | 311 ------------ example_code/item_05.py | 113 ----- example_code/item_06.py | 135 ------ example_code/item_07.py | 85 ---- example_code/item_08.py | 99 ---- example_code/item_09.py | 113 ----- example_code/item_10.py | 242 ---------- example_code/item_11.py | 131 ----- example_code/item_12.py | 99 ---- example_code/item_13.py | 147 ------ example_code/item_14.py | 166 ------- example_code/item_15.py | 186 ------- example_code/item_15_example_01.py | 25 - example_code/item_15_example_03.py | 28 -- example_code/item_15_example_05.py | 25 - example_code/item_15_example_07.py | 28 -- example_code/item_15_example_17.py | 68 --- example_code/item_16.py | 198 -------- example_code/item_17.py | 99 ---- example_code/item_18.py | 191 -------- example_code/item_19.py | 142 ------ example_code/item_20.py | 143 ------ example_code/item_21.py | 141 ------ example_code/item_22.py | 100 ---- example_code/item_23.py | 161 ------- example_code/item_24.py | 128 ----- example_code/item_24_example_09.py | 41 -- example_code/item_25.py | 238 --------- example_code/item_26.py | 126 ----- example_code/item_27.py | 90 ---- example_code/item_28.py | 93 ---- example_code/item_29.py | 137 ------ example_code/item_30.py | 113 ----- example_code/item_31.py | 209 -------- example_code/item_32.py | 76 --- example_code/item_33.py | 117 ----- example_code/item_34.py | 171 ------- example_code/item_35.py | 157 ------ example_code/item_36.py | 162 ------- example_code/item_37.py | 230 --------- example_code/item_38.py | 138 ------ example_code/item_39.py | 209 -------- example_code/item_40.py | 174 ------- example_code/item_41.py | 177 ------- example_code/item_42.py | 214 --------- example_code/item_43.py | 208 -------- example_code/item_44.py | 192 -------- example_code/item_45.py | 204 -------- example_code/item_46.py | 227 --------- example_code/item_47.py | 195 -------- example_code/item_48.py | 303 ------------ example_code/item_49.py | 212 -------- example_code/item_50.py | 182 ------- example_code/item_51.py | 243 ---------- example_code/item_52.py | 182 ------- example_code/item_53.py | 142 ------ example_code/item_54.py | 144 ------ example_code/item_55.py | 325 ------------- example_code/item_56.py | 228 --------- example_code/item_57.py | 211 -------- example_code/item_58.py | 417 ---------------- example_code/item_59.py | 207 -------- example_code/item_60.py | 247 ---------- example_code/item_61.py | 454 ------------------ example_code/item_62.py | 295 ------------ example_code/item_63.py | 252 ---------- example_code/item_64/parallel/my_module.py | 23 - example_code/item_64/parallel/run_parallel.py | 38 -- example_code/item_64/parallel/run_serial.py | 36 -- example_code/item_64/parallel/run_threads.py | 38 -- example_code/item_65.py | 197 -------- example_code/item_66.py | 136 ------ example_code/item_67.py | 125 ----- example_code/item_68.py | 224 --------- example_code/item_69.py | 99 ---- example_code/item_70.py | 141 ------ example_code/item_71.py | 264 ---------- example_code/item_72.py | 115 ----- example_code/item_73.py | 384 --------------- example_code/item_74.py | 208 -------- example_code/item_75.py | 123 ----- example_code/item_76/testing/assert_test.py | 32 -- .../item_76/testing/data_driven_test.py | 42 -- example_code/item_76/testing/helper_test.py | 69 --- example_code/item_76/testing/utils.py | 24 - .../item_76/testing/utils_error_test.py | 30 -- example_code/item_76/testing/utils_test.py | 31 -- .../item_77/testing/environment_test.py | 34 -- .../item_77/testing/integration_test.py | 39 -- example_code/item_78.py | 307 ------------ example_code/item_79.py | 166 ------- .../item_80/debugging/always_breakpoint.py | 35 -- .../debugging/conditional_breakpoint.py | 34 -- example_code/item_80/debugging/my_module.py | 31 -- .../debugging/postmortem_breakpoint.py | 34 -- example_code/item_81/tracemalloc/top_n.py | 29 -- example_code/item_81/tracemalloc/using_gc.py | 31 -- .../item_81/tracemalloc/waste_memory.py | 35 -- .../item_81/tracemalloc/with_trace.py | 30 -- example_code/item_84.py | 112 ----- example_code/item_84_example_06.py | 26 - example_code/item_84_example_07.py | 38 -- .../item_85/api_package/api_consumer.py | 31 -- example_code/item_85/api_package/main.py | 17 - .../item_85/api_package/mypackage/__init__.py | 21 - .../item_85/api_package/mypackage/models.py | 22 - .../item_85/api_package/mypackage/utils.py | 27 -- .../namespace_package/analysis/__init__.py | 17 - .../namespace_package/analysis/utils.py | 22 - .../namespace_package/frontend/__init__.py | 17 - .../namespace_package/frontend/utils.py | 21 - .../item_85/namespace_package/main.py | 21 - .../item_85/namespace_package/main2.py | 20 - .../item_85/namespace_package/main3.py | 22 - .../item_85/namespace_package/main4.py | 23 - example_code/item_86.py | 62 --- .../item_86/module_scope/db_connection.py | 29 -- example_code/item_86/module_scope/dev_main.py | 21 - .../item_86/module_scope/prod_main.py | 21 - example_code/item_87.py | 189 -------- .../item_88/recursive_import_bad/app.py | 24 - .../item_88/recursive_import_bad/dialog.py | 26 - .../item_88/recursive_import_bad/main.py | 17 - .../item_88/recursive_import_dynamic/app.py | 24 - .../recursive_import_dynamic/dialog.py | 31 -- .../item_88/recursive_import_dynamic/main.py | 17 - .../recursive_import_nosideeffects/app.py | 26 - .../recursive_import_nosideeffects/dialog.py | 29 -- .../recursive_import_nosideeffects/main.py | 23 - .../item_88/recursive_import_ordering/app.py | 24 - .../recursive_import_ordering/dialog.py | 26 - .../item_88/recursive_import_ordering/main.py | 17 - example_code/item_89.py | 234 --------- example_code/item_90.py | 191 -------- example_code/item_90_example_02.py | 25 - example_code/item_90_example_04.py | 25 - example_code/item_90_example_08.py | 35 -- example_code/item_90_example_10.py | 43 -- example_code/item_90_example_12.py | 28 -- example_code/item_90_example_14.py | 31 -- example_code/item_90_example_17.py | 31 -- 143 files changed, 16387 deletions(-) delete mode 100755 example_code/item_01.py delete mode 100755 example_code/item_03.py delete mode 100755 example_code/item_04.py delete mode 100755 example_code/item_05.py delete mode 100755 example_code/item_06.py delete mode 100755 example_code/item_07.py delete mode 100755 example_code/item_08.py delete mode 100755 example_code/item_09.py delete mode 100755 example_code/item_10.py delete mode 100755 example_code/item_11.py delete mode 100755 example_code/item_12.py delete mode 100755 example_code/item_13.py delete mode 100755 example_code/item_14.py delete mode 100755 example_code/item_15.py delete mode 100755 example_code/item_15_example_01.py delete mode 100755 example_code/item_15_example_03.py delete mode 100755 example_code/item_15_example_05.py delete mode 100755 example_code/item_15_example_07.py delete mode 100755 example_code/item_15_example_17.py delete mode 100755 example_code/item_16.py delete mode 100755 example_code/item_17.py delete mode 100755 example_code/item_18.py delete mode 100755 example_code/item_19.py delete mode 100755 example_code/item_20.py delete mode 100755 example_code/item_21.py delete mode 100755 example_code/item_22.py delete mode 100755 example_code/item_23.py delete mode 100755 example_code/item_24.py delete mode 100755 example_code/item_24_example_09.py delete mode 100755 example_code/item_25.py delete mode 100755 example_code/item_26.py delete mode 100755 example_code/item_27.py delete mode 100755 example_code/item_28.py delete mode 100755 example_code/item_29.py delete mode 100755 example_code/item_30.py delete mode 100755 example_code/item_31.py delete mode 100755 example_code/item_32.py delete mode 100755 example_code/item_33.py delete mode 100755 example_code/item_34.py delete mode 100755 example_code/item_35.py delete mode 100755 example_code/item_36.py delete mode 100755 example_code/item_37.py delete mode 100755 example_code/item_38.py delete mode 100755 example_code/item_39.py delete mode 100755 example_code/item_40.py delete mode 100755 example_code/item_41.py delete mode 100755 example_code/item_42.py delete mode 100755 example_code/item_43.py delete mode 100755 example_code/item_44.py delete mode 100755 example_code/item_45.py delete mode 100755 example_code/item_46.py delete mode 100755 example_code/item_47.py delete mode 100755 example_code/item_48.py delete mode 100755 example_code/item_49.py delete mode 100755 example_code/item_50.py delete mode 100755 example_code/item_51.py delete mode 100755 example_code/item_52.py delete mode 100755 example_code/item_53.py delete mode 100755 example_code/item_54.py delete mode 100755 example_code/item_55.py delete mode 100755 example_code/item_56.py delete mode 100755 example_code/item_57.py delete mode 100755 example_code/item_58.py delete mode 100755 example_code/item_59.py delete mode 100755 example_code/item_60.py delete mode 100755 example_code/item_61.py delete mode 100755 example_code/item_62.py delete mode 100755 example_code/item_63.py delete mode 100755 example_code/item_64/parallel/my_module.py delete mode 100755 example_code/item_64/parallel/run_parallel.py delete mode 100755 example_code/item_64/parallel/run_serial.py delete mode 100755 example_code/item_64/parallel/run_threads.py delete mode 100755 example_code/item_65.py delete mode 100755 example_code/item_66.py delete mode 100755 example_code/item_67.py delete mode 100755 example_code/item_68.py delete mode 100755 example_code/item_69.py delete mode 100755 example_code/item_70.py delete mode 100755 example_code/item_71.py delete mode 100755 example_code/item_72.py delete mode 100755 example_code/item_73.py delete mode 100755 example_code/item_74.py delete mode 100755 example_code/item_75.py delete mode 100755 example_code/item_76/testing/assert_test.py delete mode 100755 example_code/item_76/testing/data_driven_test.py delete mode 100755 example_code/item_76/testing/helper_test.py delete mode 100755 example_code/item_76/testing/utils.py delete mode 100755 example_code/item_76/testing/utils_error_test.py delete mode 100755 example_code/item_76/testing/utils_test.py delete mode 100755 example_code/item_77/testing/environment_test.py delete mode 100755 example_code/item_77/testing/integration_test.py delete mode 100755 example_code/item_78.py delete mode 100755 example_code/item_79.py delete mode 100755 example_code/item_80/debugging/always_breakpoint.py delete mode 100755 example_code/item_80/debugging/conditional_breakpoint.py delete mode 100755 example_code/item_80/debugging/my_module.py delete mode 100755 example_code/item_80/debugging/postmortem_breakpoint.py delete mode 100755 example_code/item_81/tracemalloc/top_n.py delete mode 100755 example_code/item_81/tracemalloc/using_gc.py delete mode 100755 example_code/item_81/tracemalloc/waste_memory.py delete mode 100755 example_code/item_81/tracemalloc/with_trace.py delete mode 100755 example_code/item_84.py delete mode 100755 example_code/item_84_example_06.py delete mode 100755 example_code/item_84_example_07.py delete mode 100755 example_code/item_85/api_package/api_consumer.py delete mode 100755 example_code/item_85/api_package/main.py delete mode 100755 example_code/item_85/api_package/mypackage/__init__.py delete mode 100755 example_code/item_85/api_package/mypackage/models.py delete mode 100755 example_code/item_85/api_package/mypackage/utils.py delete mode 100755 example_code/item_85/namespace_package/analysis/__init__.py delete mode 100755 example_code/item_85/namespace_package/analysis/utils.py delete mode 100755 example_code/item_85/namespace_package/frontend/__init__.py delete mode 100755 example_code/item_85/namespace_package/frontend/utils.py delete mode 100755 example_code/item_85/namespace_package/main.py delete mode 100755 example_code/item_85/namespace_package/main2.py delete mode 100755 example_code/item_85/namespace_package/main3.py delete mode 100755 example_code/item_85/namespace_package/main4.py delete mode 100755 example_code/item_86.py delete mode 100755 example_code/item_86/module_scope/db_connection.py delete mode 100755 example_code/item_86/module_scope/dev_main.py delete mode 100755 example_code/item_86/module_scope/prod_main.py delete mode 100755 example_code/item_87.py delete mode 100755 example_code/item_88/recursive_import_bad/app.py delete mode 100755 example_code/item_88/recursive_import_bad/dialog.py delete mode 100755 example_code/item_88/recursive_import_bad/main.py delete mode 100755 example_code/item_88/recursive_import_dynamic/app.py delete mode 100755 example_code/item_88/recursive_import_dynamic/dialog.py delete mode 100755 example_code/item_88/recursive_import_dynamic/main.py delete mode 100755 example_code/item_88/recursive_import_nosideeffects/app.py delete mode 100755 example_code/item_88/recursive_import_nosideeffects/dialog.py delete mode 100755 example_code/item_88/recursive_import_nosideeffects/main.py delete mode 100755 example_code/item_88/recursive_import_ordering/app.py delete mode 100755 example_code/item_88/recursive_import_ordering/dialog.py delete mode 100755 example_code/item_88/recursive_import_ordering/main.py delete mode 100755 example_code/item_89.py delete mode 100755 example_code/item_90.py delete mode 100755 example_code/item_90_example_02.py delete mode 100755 example_code/item_90_example_04.py delete mode 100755 example_code/item_90_example_08.py delete mode 100755 example_code/item_90_example_10.py delete mode 100755 example_code/item_90_example_12.py delete mode 100755 example_code/item_90_example_14.py delete mode 100755 example_code/item_90_example_17.py diff --git a/example_code/item_01.py b/example_code/item_01.py deleted file mode 100755 index 585a9c5..0000000 --- a/example_code/item_01.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -import sys -print(sys.version_info) -print(sys.version) diff --git a/example_code/item_03.py b/example_code/item_03.py deleted file mode 100755 index 24eb0a7..0000000 --- a/example_code/item_03.py +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -a = b'h\x65llo' -print(list(a)) -print(a) - - -# Example 2 -a = 'a\u0300 propos' -print(list(a)) -print(a) - - -# Example 3 -def to_str(bytes_or_str): - if isinstance(bytes_or_str, bytes): - value = bytes_or_str.decode('utf-8') - else: - value = bytes_or_str - return value # Instance of str - -print(repr(to_str(b'foo'))) -print(repr(to_str('bar'))) - - -# Example 4 -def to_bytes(bytes_or_str): - if isinstance(bytes_or_str, str): - value = bytes_or_str.encode('utf-8') - else: - value = bytes_or_str - return value # Instance of bytes - -print(repr(to_bytes(b'foo'))) -print(repr(to_bytes('bar'))) - - -# Example 5 -print(b'one' + b'two') -print('one' + 'two') - - -# Example 6 -try: - b'one' + 'two' -except: - logging.exception('Expected') -else: - assert False - - -# Example 7 -try: - 'one' + b'two' -except: - logging.exception('Expected') -else: - assert False - - -# Example 8 -assert b'red' > b'blue' -assert 'red' > 'blue' - - -# Example 9 -try: - assert 'red' > b'blue' -except: - logging.exception('Expected') -else: - assert False - - -# Example 10 -try: - assert b'blue' < 'red' -except: - logging.exception('Expected') -else: - assert False - - -# Example 11 -print(b'foo' == 'foo') - - -# Example 12 -print(b'red %s' % b'blue') -print('red %s' % 'blue') - - -# Example 13 -try: - print(b'red %s' % 'blue') -except: - logging.exception('Expected') -else: - assert False - - -# Example 14 -print('red %s' % b'blue') - - -# Example 15 -try: - with open('data.bin', 'w') as f: - f.write(b'\xf1\xf2\xf3\xf4\xf5') -except: - logging.exception('Expected') -else: - assert False - - -# Example 16 -with open('data.bin', 'wb') as f: - f.write(b'\xf1\xf2\xf3\xf4\xf5') - - -# Example 17 -try: - # Silently force UTF-8 here to make sure this test fails on - # all platforms. cp1252 considers these bytes valid on Windows. - real_open = open - def open(*args, **kwargs): - kwargs['encoding'] = 'utf-8' - return real_open(*args, **kwargs) - - with open('data.bin', 'r') as f: - data = f.read() -except: - logging.exception('Expected') -else: - assert False - - -# Example 18 -# Restore the overloaded open above. -open = real_open - -with open('data.bin', 'rb') as f: - data = f.read() - -assert data == b'\xf1\xf2\xf3\xf4\xf5' - - -# Example 19 -with open('data.bin', 'r', encoding='cp1252') as f: - data = f.read() - -assert data == 'ñòóôõ' diff --git a/example_code/item_04.py b/example_code/item_04.py deleted file mode 100755 index d1fca5d..0000000 --- a/example_code/item_04.py +++ /dev/null @@ -1,311 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -a = 0b10111011 -b = 0xc5f -print('Binary is %d, hex is %d' % (a, b)) - - -# Example 2 -key = 'my_var' -value = 1.234 -formatted = '%-10s = %.2f' % (key, value) -print(formatted) - - -# Example 3 -try: - reordered_tuple = '%-10s = %.2f' % (value, key) -except: - logging.exception('Expected') -else: - assert False - - -# Example 4 -try: - reordered_string = '%.2f = %-10s' % (key, value) -except: - logging.exception('Expected') -else: - assert False - - -# Example 5 -pantry = [ - ('avocados', 1.25), - ('bananas', 2.5), - ('cherries', 15), -] -for i, (item, count) in enumerate(pantry): - print('#%d: %-10s = %.2f' % (i, item, count)) - - -# Example 6 -for i, (item, count) in enumerate(pantry): - print('#%d: %-10s = %d' % ( - i + 1, - item.title(), - round(count))) - - -# Example 7 -template = '%s loves food. See %s cook.' -name = 'Max' -formatted = template % (name, name) -print(formatted) - - -# Example 8 -name = 'brad' -formatted = template % (name.title(), name.title()) -print(formatted) - - -# Example 9 -key = 'my_var' -value = 1.234 - -old_way = '%-10s = %.2f' % (key, value) - -new_way = '%(key)-10s = %(value).2f' % { - 'key': key, 'value': value} # Original - -reordered = '%(key)-10s = %(value).2f' % { - 'value': value, 'key': key} # Swapped - -assert old_way == new_way == reordered - - -# Example 10 -name = 'Max' - -template = '%s loves food. See %s cook.' -before = template % (name, name) # Tuple - -template = '%(name)s loves food. See %(name)s cook.' -after = template % {'name': name} # Dictionary - -assert before == after - - -# Example 11 -for i, (item, count) in enumerate(pantry): - before = '#%d: %-10s = %d' % ( - i + 1, - item.title(), - round(count)) - - after = '#%(loop)d: %(item)-10s = %(count)d' % { - 'loop': i + 1, - 'item': item.title(), - 'count': round(count), - } - - assert before == after - - -# Example 12 -soup = 'lentil' -formatted = 'Today\'s soup is %(soup)s.' % {'soup': soup} -print(formatted) - - -# Example 13 -menu = { - 'soup': 'lentil', - 'oyster': 'kumamoto', - 'special': 'schnitzel', -} -template = ('Today\'s soup is %(soup)s, ' - 'buy one get two %(oyster)s oysters, ' - 'and our special entrée is %(special)s.') -formatted = template % menu -print(formatted) - - -# Example 14 -a = 1234.5678 -formatted = format(a, ',.2f') -print(formatted) - -b = 'my string' -formatted = format(b, '^20s') -print('*', formatted, '*') - - -# Example 15 -key = 'my_var' -value = 1.234 - -formatted = '{} = {}'.format(key, value) -print(formatted) - - -# Example 16 -formatted = '{:<10} = {:.2f}'.format(key, value) -print(formatted) - - -# Example 17 -print('%.2f%%' % 12.5) -print('{} replaces {{}}'.format(1.23)) - - -# Example 18 -formatted = '{1} = {0}'.format(key, value) -print(formatted) - - -# Example 19 -formatted = '{0} loves food. See {0} cook.'.format(name) -print(formatted) - - -# Example 20 -for i, (item, count) in enumerate(pantry): - old_style = '#%d: %-10s = %d' % ( - i + 1, - item.title(), - round(count)) - - new_style = '#{}: {:<10s} = {}'.format( - i + 1, - item.title(), - round(count)) - - assert old_style == new_style - - -# Example 21 -formatted = 'First letter is {menu[oyster][0]!r}'.format( - menu=menu) -print(formatted) - - -# Example 22 -old_template = ( - 'Today\'s soup is %(soup)s, ' - 'buy one get two %(oyster)s oysters, ' - 'and our special entrée is %(special)s.') -old_formatted = old_template % { - 'soup': 'lentil', - 'oyster': 'kumamoto', - 'special': 'schnitzel', -} - -new_template = ( - 'Today\'s soup is {soup}, ' - 'buy one get two {oyster} oysters, ' - 'and our special entrée is {special}.') -new_formatted = new_template.format( - soup='lentil', - oyster='kumamoto', - special='schnitzel', -) - -assert old_formatted == new_formatted - - -# Example 23 -key = 'my_var' -value = 1.234 - -formatted = f'{key} = {value}' -print(formatted) - - -# Example 24 -formatted = f'{key!r:<10} = {value:.2f}' -print(formatted) - - -# Example 25 -f_string = f'{key:<10} = {value:.2f}' - -c_tuple = '%-10s = %.2f' % (key, value) - -str_args = '{:<10} = {:.2f}'.format(key, value) - -str_kw = '{key:<10} = {value:.2f}'.format(key=key, value=value) - -c_dict = '%(key)-10s = %(value).2f' % {'key': key, 'value': value} - -assert c_tuple == c_dict == f_string -assert str_args == str_kw == f_string - - -# Example 26 -for i, (item, count) in enumerate(pantry): - old_style = '#%d: %-10s = %d' % ( - i + 1, - item.title(), - round(count)) - - new_style = '#{}: {:<10s} = {}'.format( - i + 1, - item.title(), - round(count)) - - f_string = f'#{i+1}: {item.title():<10s} = {round(count)}' - - assert old_style == new_style == f_string - - -# Example 27 -for i, (item, count) in enumerate(pantry): - print(f'#{i+1}: ' - f'{item.title():<10s} = ' - f'{round(count)}') - - -# Example 28 -places = 3 -number = 1.23456 -print(f'My number is {number:.{places}f}') diff --git a/example_code/item_05.py b/example_code/item_05.py deleted file mode 100755 index ec407db..0000000 --- a/example_code/item_05.py +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -from urllib.parse import parse_qs - -my_values = parse_qs('red=5&blue=0&green=', - keep_blank_values=True) -print(repr(my_values)) - - -# Example 2 -print('Red: ', my_values.get('red')) -print('Green: ', my_values.get('green')) -print('Opacity: ', my_values.get('opacity')) - - -# Example 3 -# For query string 'red=5&blue=0&green=' -red = my_values.get('red', [''])[0] or 0 -green = my_values.get('green', [''])[0] or 0 -opacity = my_values.get('opacity', [''])[0] or 0 -print(f'Red: {red!r}') -print(f'Green: {green!r}') -print(f'Opacity: {opacity!r}') - - -# Example 4 -red = int(my_values.get('red', [''])[0] or 0) -green = int(my_values.get('green', [''])[0] or 0) -opacity = int(my_values.get('opacity', [''])[0] or 0) -print(f'Red: {red!r}') -print(f'Green: {green!r}') -print(f'Opacity: {opacity!r}') - - -# Example 5 -red_str = my_values.get('red', ['']) -red = int(red_str[0]) if red_str[0] else 0 -green_str = my_values.get('green', ['']) -green = int(green_str[0]) if green_str[0] else 0 -opacity_str = my_values.get('opacity', ['']) -opacity = int(opacity_str[0]) if opacity_str[0] else 0 -print(f'Red: {red!r}') -print(f'Green: {green!r}') -print(f'Opacity: {opacity!r}') - - -# Example 6 -green_str = my_values.get('green', ['']) -if green_str[0]: - green = int(green_str[0]) -else: - green = 0 -print(f'Green: {green!r}') - - -# Example 7 -def get_first_int(values, key, default=0): - found = values.get(key, ['']) - if found[0]: - return int(found[0]) - return default - - -# Example 8 -green = get_first_int(my_values, 'green') -print(f'Green: {green!r}') diff --git a/example_code/item_06.py b/example_code/item_06.py deleted file mode 100755 index beafb39..0000000 --- a/example_code/item_06.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -snack_calories = { - 'chips': 140, - 'popcorn': 80, - 'nuts': 190, -} -items = tuple(snack_calories.items()) -print(items) - - -# Example 2 -item = ('Peanut butter', 'Jelly') -first = item[0] -second = item[1] -print(first, 'and', second) - - -# Example 3 -try: - pair = ('Chocolate', 'Peanut butter') - pair[0] = 'Honey' -except: - logging.exception('Expected') -else: - assert False - - -# Example 4 -item = ('Peanut butter', 'Jelly') -first, second = item # Unpacking -print(first, 'and', second) - - -# Example 5 -favorite_snacks = { - 'salty': ('pretzels', 100), - 'sweet': ('cookies', 180), - 'veggie': ('carrots', 20), -} - -((type1, (name1, cals1)), - (type2, (name2, cals2)), - (type3, (name3, cals3))) = favorite_snacks.items() - -print(f'Favorite {type1} is {name1} with {cals1} calories') -print(f'Favorite {type2} is {name2} with {cals2} calories') -print(f'Favorite {type3} is {name3} with {cals3} calories') - - -# Example 6 -def bubble_sort(a): - for _ in range(len(a)): - for i in range(1, len(a)): - if a[i] < a[i-1]: - temp = a[i] - a[i] = a[i-1] - a[i-1] = temp - -names = ['pretzels', 'carrots', 'arugula', 'bacon'] -bubble_sort(names) -print(names) - - -# Example 7 -def bubble_sort(a): - for _ in range(len(a)): - for i in range(1, len(a)): - if a[i] < a[i-1]: - a[i-1], a[i] = a[i], a[i-1] # Swap - -names = ['pretzels', 'carrots', 'arugula', 'bacon'] -bubble_sort(names) -print(names) - - -# Example 8 -snacks = [('bacon', 350), ('donut', 240), ('muffin', 190)] -for i in range(len(snacks)): - item = snacks[i] - name = item[0] - calories = item[1] - print(f'#{i+1}: {name} has {calories} calories') - - -# Example 9 -for rank, (name, calories) in enumerate(snacks, 1): - print(f'#{rank}: {name} has {calories} calories') diff --git a/example_code/item_07.py b/example_code/item_07.py deleted file mode 100755 index c1f4239..0000000 --- a/example_code/item_07.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -from random import randint - -random_bits = 0 -for i in range(32): - if randint(0, 1): - random_bits |= 1 << i - -print(bin(random_bits)) - - -# Example 2 -flavor_list = ['vanilla', 'chocolate', 'pecan', 'strawberry'] -for flavor in flavor_list: - print(f'{flavor} is delicious') - - -# Example 3 -for i in range(len(flavor_list)): - flavor = flavor_list[i] - print(f'{i + 1}: {flavor}') - - -# Example 4 -it = enumerate(flavor_list) -print(next(it)) -print(next(it)) - - -# Example 5 -for i, flavor in enumerate(flavor_list): - print(f'{i + 1}: {flavor}') - - -# Example 6 -for i, flavor in enumerate(flavor_list, 1): - print(f'{i}: {flavor}') diff --git a/example_code/item_08.py b/example_code/item_08.py deleted file mode 100755 index c22151c..0000000 --- a/example_code/item_08.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -names = ['Cecilia', 'Lise', 'Marie'] -counts = [len(n) for n in names] -print(counts) - - -# Example 2 -longest_name = None -max_count = 0 - -for i in range(len(names)): - count = counts[i] - if count > max_count: - longest_name = names[i] - max_count = count - -print(longest_name) - - -# Example 3 -longest_name = None -max_count = 0 -for i, name in enumerate(names): - count = counts[i] - if count > max_count: - longest_name = name - max_count = count -assert longest_name == 'Cecilia' - - -# Example 4 -longest_name = None -max_count = 0 -for name, count in zip(names, counts): - if count > max_count: - longest_name = name - max_count = count -assert longest_name == 'Cecilia' - - -# Example 5 -names.append('Rosalind') -for name, count in zip(names, counts): - print(name) - - -# Example 6 -import itertools - -for name, count in itertools.zip_longest(names, counts): - print(f'{name}: {count}') diff --git a/example_code/item_09.py b/example_code/item_09.py deleted file mode 100755 index d96d033..0000000 --- a/example_code/item_09.py +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -for i in range(3): - print('Loop', i) -else: - print('Else block!') - - -# Example 2 -for i in range(3): - print('Loop', i) - if i == 1: - break -else: - print('Else block!') - - -# Example 3 -for x in []: - print('Never runs') -else: - print('For Else block!') - - -# Example 4 -while False: - print('Never runs') -else: - print('While Else block!') - - -# Example 5 -a = 4 -b = 9 - -for i in range(2, min(a, b) + 1): - print('Testing', i) - if a % i == 0 and b % i == 0: - print('Not coprime') - break -else: - print('Coprime') - - -# Example 6 -def coprime(a, b): - for i in range(2, min(a, b) + 1): - if a % i == 0 and b % i == 0: - return False - return True - -assert coprime(4, 9) -assert not coprime(3, 6) - - -# Example 7 -def coprime_alternate(a, b): - is_coprime = True - for i in range(2, min(a, b) + 1): - if a % i == 0 and b % i == 0: - is_coprime = False - break - return is_coprime - -assert coprime_alternate(4, 9) -assert not coprime_alternate(3, 6) diff --git a/example_code/item_10.py b/example_code/item_10.py deleted file mode 100755 index a106c38..0000000 --- a/example_code/item_10.py +++ /dev/null @@ -1,242 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -fresh_fruit = { - 'apple': 10, - 'banana': 8, - 'lemon': 5, -} - - -# Example 2 -def make_lemonade(count): - print(f'Making {count} lemons into lemonade') - -def out_of_stock(): - print('Out of stock!') - -count = fresh_fruit.get('lemon', 0) -if count: - make_lemonade(count) -else: - out_of_stock() - - -# Example 3 -if count := fresh_fruit.get('lemon', 0): - make_lemonade(count) -else: - out_of_stock() - - -# Example 4 -def make_cider(count): - print(f'Making cider with {count} apples') - -count = fresh_fruit.get('apple', 0) -if count >= 4: - make_cider(count) -else: - out_of_stock() - - -# Example 5 -if (count := fresh_fruit.get('apple', 0)) >= 4: - make_cider(count) -else: - out_of_stock() - - -# Example 6 -def slice_bananas(count): - print(f'Slicing {count} bananas') - return count * 4 - -class OutOfBananas(Exception): - pass - -def make_smoothies(count): - print(f'Making a smoothies with {count} banana slices') - -pieces = 0 -count = fresh_fruit.get('banana', 0) -if count >= 2: - pieces = slice_bananas(count) - -try: - smoothies = make_smoothies(pieces) -except OutOfBananas: - out_of_stock() - - -# Example 7 -count = fresh_fruit.get('banana', 0) -if count >= 2: - pieces = slice_bananas(count) -else: - pieces = 0 - -try: - smoothies = make_smoothies(pieces) -except OutOfBananas: - out_of_stock() - - -# Example 8 -pieces = 0 -if (count := fresh_fruit.get('banana', 0)) >= 2: - pieces = slice_bananas(count) - -try: - smoothies = make_smoothies(pieces) -except OutOfBananas: - out_of_stock() - - -# Example 9 -if (count := fresh_fruit.get('banana', 0)) >= 2: - pieces = slice_bananas(count) -else: - pieces = 0 - -try: - smoothies = make_smoothies(pieces) -except OutOfBananas: - out_of_stock() - - -# Example 10 -count = fresh_fruit.get('banana', 0) -if count >= 2: - pieces = slice_bananas(count) - to_enjoy = make_smoothies(pieces) -else: - count = fresh_fruit.get('apple', 0) - if count >= 4: - to_enjoy = make_cider(count) - else: - count = fresh_fruit.get('lemon', 0) - if count: - to_enjoy = make_lemonade(count) - else: - to_enjoy = 'Nothing' - - -# Example 11 -if (count := fresh_fruit.get('banana', 0)) >= 2: - pieces = slice_bananas(count) - to_enjoy = make_smoothies(pieces) -elif (count := fresh_fruit.get('apple', 0)) >= 4: - to_enjoy = make_cider(count) -elif count := fresh_fruit.get('lemon', 0): - to_enjoy = make_lemonade(count) -else: - to_enjoy = 'Nothing' - - -# Example 12 -FRUIT_TO_PICK = [ - {'apple': 1, 'banana': 3}, - {'lemon': 2, 'lime': 5}, - {'orange': 3, 'melon': 2}, -] - -def pick_fruit(): - if FRUIT_TO_PICK: - return FRUIT_TO_PICK.pop(0) - else: - return [] - -def make_juice(fruit, count): - return [(fruit, count)] - -bottles = [] -fresh_fruit = pick_fruit() -while fresh_fruit: - for fruit, count in fresh_fruit.items(): - batch = make_juice(fruit, count) - bottles.extend(batch) - fresh_fruit = pick_fruit() - -print(bottles) - - -# Example 13 -FRUIT_TO_PICK = [ - {'apple': 1, 'banana': 3}, - {'lemon': 2, 'lime': 5}, - {'orange': 3, 'melon': 2}, -] - -bottles = [] -while True: # Loop - fresh_fruit = pick_fruit() - if not fresh_fruit: # And a half - break - for fruit, count in fresh_fruit.items(): - batch = make_juice(fruit, count) - bottles.extend(batch) - -print(bottles) - - -# Example 14 -FRUIT_TO_PICK = [ - {'apple': 1, 'banana': 3}, - {'lemon': 2, 'lime': 5}, - {'orange': 3, 'melon': 2}, -] - -bottles = [] -while fresh_fruit := pick_fruit(): - for fruit, count in fresh_fruit.items(): - batch = make_juice(fruit, count) - bottles.extend(batch) - -print(bottles) diff --git a/example_code/item_11.py b/example_code/item_11.py deleted file mode 100755 index f502f34..0000000 --- a/example_code/item_11.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] -print('Middle two: ', a[3:5]) -print('All but ends:', a[1:7]) - - -# Example 2 -assert a[:5] == a[0:5] - - -# Example 3 -assert a[5:] == a[5:len(a)] - - -# Example 4 -print(a[:]) -print(a[:5]) -print(a[:-1]) -print(a[4:]) -print(a[-3:]) -print(a[2:5]) -print(a[2:-1]) -print(a[-3:-1]) - - -# Example 5 -a[:] # ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] -a[:5] # ['a', 'b', 'c', 'd', 'e'] -a[:-1] # ['a', 'b', 'c', 'd', 'e', 'f', 'g'] -a[4:] # ['e', 'f', 'g', 'h'] -a[-3:] # ['f', 'g', 'h'] -a[2:5] # ['c', 'd', 'e'] -a[2:-1] # ['c', 'd', 'e', 'f', 'g'] -a[-3:-1] # ['f', 'g'] - - -# Example 6 -first_twenty_items = a[:20] -last_twenty_items = a[-20:] - - -# Example 7 -try: - a[20] -except: - logging.exception('Expected') -else: - assert False - - -# Example 8 -b = a[3:] -print('Before: ', b) -b[1] = 99 -print('After: ', b) -print('No change:', a) - - -# Example 9 -print('Before ', a) -a[2:7] = [99, 22, 14] -print('After ', a) - - -# Example 10 -print('Before ', a) -a[2:3] = [47, 11] -print('After ', a) - - -# Example 11 -b = a[:] -assert b == a and b is not a - - -# Example 12 -b = a -print('Before a', a) -print('Before b', b) -a[:] = [101, 102, 103] -assert a is b # Still the same list object -print('After a ', a) # Now has different contents -print('After b ', b) # Same list, so same contents as a diff --git a/example_code/item_12.py b/example_code/item_12.py deleted file mode 100755 index 7744737..0000000 --- a/example_code/item_12.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -x = ['red', 'orange', 'yellow', 'green', 'blue', 'purple'] -odds = x[::2] -evens = x[1::2] -print(odds) -print(evens) - - -# Example 2 -x = b'mongoose' -y = x[::-1] -print(y) - - -# Example 3 -x = '寿司' -y = x[::-1] -print(y) - - -# Example 4 -try: - w = '寿司' - x = w.encode('utf-8') - y = x[::-1] - z = y.decode('utf-8') -except: - logging.exception('Expected') -else: - assert False - - -# Example 5 -x = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] -x[::2] # ['a', 'c', 'e', 'g'] -x[::-2] # ['h', 'f', 'd', 'b'] - - -# Example 6 -x[2::2] # ['c', 'e', 'g'] -x[-2::-2] # ['g', 'e', 'c', 'a'] -x[-2:2:-2] # ['g', 'e'] -x[2:2:-2] # [] - - -# Example 7 -y = x[::2] # ['a', 'c', 'e', 'g'] -z = y[1:-1] # ['c', 'e'] -print(x) -print(y) -print(z) diff --git a/example_code/item_13.py b/example_code/item_13.py deleted file mode 100755 index d2b9349..0000000 --- a/example_code/item_13.py +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -try: - car_ages = [0, 9, 4, 8, 7, 20, 19, 1, 6, 15] - car_ages_descending = sorted(car_ages, reverse=True) - oldest, second_oldest = car_ages_descending -except: - logging.exception('Expected') -else: - assert False - - -# Example 2 -oldest = car_ages_descending[0] -second_oldest = car_ages_descending[1] -others = car_ages_descending[2:] -print(oldest, second_oldest, others) - - -# Example 3 -oldest, second_oldest, *others = car_ages_descending -print(oldest, second_oldest, others) - - -# Example 4 -oldest, *others, youngest = car_ages_descending -print(oldest, youngest, others) - -*others, second_youngest, youngest = car_ages_descending -print(youngest, second_youngest, others) - - -# Example 5 -try: - # This will not compile - source = """*others = car_ages_descending""" - eval(source) -except: - logging.exception('Expected') -else: - assert False - - -# Example 6 -try: - # This will not compile - source = """first, *middle, *second_middle, last = [1, 2, 3, 4]""" - eval(source) -except: - logging.exception('Expected') -else: - assert False - - -# Example 7 -car_inventory = { - 'Downtown': ('Silver Shadow', 'Pinto', 'DMC'), - 'Airport': ('Skyline', 'Viper', 'Gremlin', 'Nova'), -} - -((loc1, (best1, *rest1)), - (loc2, (best2, *rest2))) = car_inventory.items() - -print(f'Best at {loc1} is {best1}, {len(rest1)} others') -print(f'Best at {loc2} is {best2}, {len(rest2)} others') - - -# Example 8 -short_list = [1, 2] -first, second, *rest = short_list -print(first, second, rest) - - -# Example 9 -it = iter(range(1, 3)) -first, second = it -print(f'{first} and {second}') - - -# Example 10 -def generate_csv(): - yield ('Date', 'Make' , 'Model', 'Year', 'Price') - for i in range(100): - yield ('2019-03-25', 'Honda', 'Fit' , '2010', '$3400') - yield ('2019-03-26', 'Ford', 'F150' , '2008', '$2400') - - -# Example 11 -all_csv_rows = list(generate_csv()) -header = all_csv_rows[0] -rows = all_csv_rows[1:] -print('CSV Header:', header) -print('Row count: ', len(rows)) - - -# Example 12 -it = generate_csv() -header, *rows = it -print('CSV Header:', header) -print('Row count: ', len(rows)) diff --git a/example_code/item_14.py b/example_code/item_14.py deleted file mode 100755 index 271663a..0000000 --- a/example_code/item_14.py +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -numbers = [93, 86, 11, 68, 70] -numbers.sort() -print(numbers) - - -# Example 2 -class Tool: - def __init__(self, name, weight): - self.name = name - self.weight = weight - - def __repr__(self): - return f'Tool({self.name!r}, {self.weight})' - -tools = [ - Tool('level', 3.5), - Tool('hammer', 1.25), - Tool('screwdriver', 0.5), - Tool('chisel', 0.25), -] - - -# Example 3 -try: - tools.sort() -except: - logging.exception('Expected') -else: - assert False - - -# Example 4 -print('Unsorted:', repr(tools)) -tools.sort(key=lambda x: x.name) -print('\nSorted: ', tools) - - -# Example 5 -tools.sort(key=lambda x: x.weight) -print('By weight:', tools) - - -# Example 6 -places = ['home', 'work', 'New York', 'Paris'] -places.sort() -print('Case sensitive: ', places) -places.sort(key=lambda x: x.lower()) -print('Case insensitive:', places) - - -# Example 7 -power_tools = [ - Tool('drill', 4), - Tool('circular saw', 5), - Tool('jackhammer', 40), - Tool('sander', 4), -] - - -# Example 8 -saw = (5, 'circular saw') -jackhammer = (40, 'jackhammer') -assert not (jackhammer < saw) # Matches expectations - - -# Example 9 -drill = (4, 'drill') -sander = (4, 'sander') -assert drill[0] == sander[0] # Same weight -assert drill[1] < sander[1] # Alphabetically less -assert drill < sander # Thus, drill comes first - - -# Example 10 -power_tools.sort(key=lambda x: (x.weight, x.name)) -print(power_tools) - - -# Example 11 -power_tools.sort(key=lambda x: (x.weight, x.name), - reverse=True) # Makes all criteria descending -print(power_tools) - - -# Example 12 -power_tools.sort(key=lambda x: (-x.weight, x.name)) -print(power_tools) - - -# Example 13 -try: - power_tools.sort(key=lambda x: (x.weight, -x.name), - reverse=True) -except: - logging.exception('Expected') -else: - assert False - - -# Example 14 -power_tools.sort(key=lambda x: x.name) # Name ascending - -power_tools.sort(key=lambda x: x.weight, # Weight descending - reverse=True) - -print(power_tools) - - -# Example 15 -power_tools.sort(key=lambda x: x.name) -print(power_tools) - - -# Example 16 -power_tools.sort(key=lambda x: x.weight, - reverse=True) -print(power_tools) diff --git a/example_code/item_15.py b/example_code/item_15.py deleted file mode 100755 index ef1ab90..0000000 --- a/example_code/item_15.py +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 2 -baby_names = { - 'cat': 'kitten', - 'dog': 'puppy', -} -print(baby_names) - - -# Example 4 -print(list(baby_names.keys())) -print(list(baby_names.values())) -print(list(baby_names.items())) -print(baby_names.popitem()) # Last item inserted - - -# Example 6 -def my_func(**kwargs): - for key, value in kwargs.items(): - print(f'{key} = {value}') - -my_func(goose='gosling', kangaroo='joey') - - -# Example 8 -class MyClass: - def __init__(self): - self.alligator = 'hatchling' - self.elephant = 'calf' - -a = MyClass() -for key, value in a.__dict__.items(): - print(f'{key} = {value}') - - -# Example 9 -votes = { - 'otter': 1281, - 'polar bear': 587, - 'fox': 863, -} - - -# Example 10 -def populate_ranks(votes, ranks): - names = list(votes.keys()) - names.sort(key=votes.get, reverse=True) - for i, name in enumerate(names, 1): - ranks[name] = i - - -# Example 11 -def get_winner(ranks): - return next(iter(ranks)) - - -# Example 12 -ranks = {} -populate_ranks(votes, ranks) -print(ranks) -winner = get_winner(ranks) -print(winner) - - -# Example 13 -from collections.abc import MutableMapping - -class SortedDict(MutableMapping): - def __init__(self): - self.data = {} - - def __getitem__(self, key): - return self.data[key] - - def __setitem__(self, key, value): - self.data[key] = value - - def __delitem__(self, key): - del self.data[key] - - def __iter__(self): - keys = list(self.data.keys()) - keys.sort() - for key in keys: - yield key - - def __len__(self): - return len(self.data) - -my_dict = SortedDict() -my_dict['otter'] = 1 -my_dict['cheeta'] = 2 -my_dict['anteater'] = 3 -my_dict['deer'] = 4 - -assert my_dict['otter'] == 1 - -assert 'cheeta' in my_dict -del my_dict['cheeta'] -assert 'cheeta' not in my_dict - -expected = [('anteater', 3), ('deer', 4), ('otter', 1)] -assert list(my_dict.items()) == expected - -assert not isinstance(my_dict, dict) - - -# Example 14 -sorted_ranks = SortedDict() -populate_ranks(votes, sorted_ranks) -print(sorted_ranks.data) -winner = get_winner(sorted_ranks) -print(winner) - - -# Example 15 -def get_winner(ranks): - for name, rank in ranks.items(): - if rank == 1: - return name - -winner = get_winner(sorted_ranks) -print(winner) - - -# Example 16 -try: - def get_winner(ranks): - if not isinstance(ranks, dict): - raise TypeError('must provide a dict instance') - return next(iter(ranks)) - - assert get_winner(ranks) == 'otter' - - get_winner(sorted_ranks) -except: - logging.exception('Expected') -else: - assert False diff --git a/example_code/item_15_example_01.py b/example_code/item_15_example_01.py deleted file mode 100755 index a797385..0000000 --- a/example_code/item_15_example_01.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3.5 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -# Example 1 -# Python 3.5 -baby_names = { - 'cat': 'kitten', - 'dog': 'puppy', -} -print(baby_names) diff --git a/example_code/item_15_example_03.py b/example_code/item_15_example_03.py deleted file mode 100755 index 39c54b1..0000000 --- a/example_code/item_15_example_03.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3.5 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -# Example 3 -# Python 3.5 -baby_names = { - 'cat': 'kitten', - 'dog': 'puppy', -} -print(list(baby_names.keys())) -print(list(baby_names.values())) -print(list(baby_names.items())) -print(baby_names.popitem()) # Randomly chooses an item diff --git a/example_code/item_15_example_05.py b/example_code/item_15_example_05.py deleted file mode 100755 index 5280de0..0000000 --- a/example_code/item_15_example_05.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3.5 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -# Example 5 -# Python 3.5 -def my_func(**kwargs): - for key, value in kwargs.items(): - print('%s = %s' % (key, value)) - -my_func(goose='gosling', kangaroo='joey') diff --git a/example_code/item_15_example_07.py b/example_code/item_15_example_07.py deleted file mode 100755 index 7073d5c..0000000 --- a/example_code/item_15_example_07.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3.5 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -# Example 7 -# Python 3.5 -class MyClass: - def __init__(self): - self.alligator = 'hatchling' - self.elephant = 'calf' - -a = MyClass() -for key, value in a.__dict__.items(): - print('%s = %s' % (key, value)) diff --git a/example_code/item_15_example_17.py b/example_code/item_15_example_17.py deleted file mode 100755 index 83fa498..0000000 --- a/example_code/item_15_example_17.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -# Example 17 -# Check types in this file with: python -m mypy - -from typing import Dict, MutableMapping - -def populate_ranks(votes: Dict[str, int], - ranks: Dict[str, int]) -> None: - names = list(votes.keys()) - names.sort(key=votes.get, reverse=True) - for i, name in enumerate(names, 1): - ranks[name] = i - -def get_winner(ranks: Dict[str, int]) -> str: - return next(iter(ranks)) - -from typing import Iterator, MutableMapping - -class SortedDict(MutableMapping[str, int]): - def __init__(self) -> None: - self.data: Dict[str, int] = {} - - def __getitem__(self, key: str) -> int: - return self.data[key] - - def __setitem__(self, key: str, value: int) -> None: - self.data[key] = value - - def __delitem__(self, key: str) -> None: - del self.data[key] - - def __iter__(self) -> Iterator[str]: - keys = list(self.data.keys()) - keys.sort() - for key in keys: - yield key - - def __len__(self) -> int: - return len(self.data) - -votes = { - 'otter': 1281, - 'polar bear': 587, - 'fox': 863, -} - -sorted_ranks = SortedDict() -populate_ranks(votes, sorted_ranks) -print(sorted_ranks.data) -winner = get_winner(sorted_ranks) -print(winner) diff --git a/example_code/item_16.py b/example_code/item_16.py deleted file mode 100755 index cf616ad..0000000 --- a/example_code/item_16.py +++ /dev/null @@ -1,198 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -counters = { - 'pumpernickel': 2, - 'sourdough': 1, -} - - -# Example 2 -key = 'wheat' - -if key in counters: - count = counters[key] -else: - count = 0 - -counters[key] = count + 1 - -print(counters) - - -# Example 3 -key = 'brioche' - -try: - count = counters[key] -except KeyError: - count = 0 - -counters[key] = count + 1 - -print(counters) - - -# Example 4 -key = 'multigrain' - -count = counters.get(key, 0) -counters[key] = count + 1 - -print(counters) - - -# Example 5 -key = 'baguette' - -if key not in counters: - counters[key] = 0 -counters[key] += 1 - -key = 'ciabatta' - -if key in counters: - counters[key] += 1 -else: - counters[key] = 1 - -key = 'ciabatta' - -try: - counters[key] += 1 -except KeyError: - counters[key] = 1 - -print(counters) - - -# Example 6 -votes = { - 'baguette': ['Bob', 'Alice'], - 'ciabatta': ['Coco', 'Deb'], -} - -key = 'brioche' -who = 'Elmer' - -if key in votes: - names = votes[key] -else: - votes[key] = names = [] - -names.append(who) -print(votes) - - -# Example 7 -key = 'rye' -who = 'Felix' - -try: - names = votes[key] -except KeyError: - votes[key] = names = [] - -names.append(who) - -print(votes) - - -# Example 8 -key = 'wheat' -who = 'Gertrude' - -names = votes.get(key) -if names is None: - votes[key] = names = [] - -names.append(who) - -print(votes) - - -# Example 9 -key = 'brioche' -who = 'Hugh' - -if (names := votes.get(key)) is None: - votes[key] = names = [] - -names.append(who) - -print(votes) - - -# Example 10 -key = 'cornbread' -who = 'Kirk' - -names = votes.setdefault(key, []) -names.append(who) - -print(votes) - - -# Example 11 -data = {} -key = 'foo' -value = [] -data.setdefault(key, value) -print('Before:', data) -value.append('hello') -print('After: ', data) - - -# Example 12 -key = 'dutch crunch' - -count = counters.setdefault(key, 0) -counters[key] = count + 1 - -print(counters) diff --git a/example_code/item_17.py b/example_code/item_17.py deleted file mode 100755 index b0e86ff..0000000 --- a/example_code/item_17.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -visits = { - 'Mexico': {'Tulum', 'Puerto Vallarta'}, - 'Japan': {'Hakone'}, -} - - -# Example 2 -visits.setdefault('France', set()).add('Arles') # Short - -if (japan := visits.get('Japan')) is None: # Long - visits['Japan'] = japan = set() -japan.add('Kyoto') -original_print = print -print = pprint - -print(visits) -print = original_print - - -# Example 3 -class Visits: - def __init__(self): - self.data = {} - - def add(self, country, city): - city_set = self.data.setdefault(country, set()) - city_set.add(city) - - -# Example 4 -visits = Visits() -visits.add('Russia', 'Yekaterinburg') -visits.add('Tanzania', 'Zanzibar') -print(visits.data) - - -# Example 5 -from collections import defaultdict - -class Visits: - def __init__(self): - self.data = defaultdict(set) - - def add(self, country, city): - self.data[country].add(city) - -visits = Visits() -visits.add('England', 'Bath') -visits.add('England', 'London') -print(visits.data) diff --git a/example_code/item_18.py b/example_code/item_18.py deleted file mode 100755 index 2d9814f..0000000 --- a/example_code/item_18.py +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -pictures = {} -path = 'profile_1234.png' - -with open(path, 'wb') as f: - f.write(b'image data here 1234') - -if (handle := pictures.get(path)) is None: - try: - handle = open(path, 'a+b') - except OSError: - print(f'Failed to open path {path}') - raise - else: - pictures[path] = handle - -handle.seek(0) -image_data = handle.read() - -print(pictures) -print(image_data) - - -# Example 2 -# Examples using in and KeyError -pictures = {} -path = 'profile_9991.png' - -with open(path, 'wb') as f: - f.write(b'image data here 9991') - -if path in pictures: - handle = pictures[path] -else: - try: - handle = open(path, 'a+b') - except OSError: - print(f'Failed to open path {path}') - raise - else: - pictures[path] = handle - -handle.seek(0) -image_data = handle.read() - -print(pictures) -print(image_data) - -pictures = {} -path = 'profile_9922.png' - -with open(path, 'wb') as f: - f.write(b'image data here 9991') - -try: - handle = pictures[path] -except KeyError: - try: - handle = open(path, 'a+b') - except OSError: - print(f'Failed to open path {path}') - raise - else: - pictures[path] = handle - -handle.seek(0) -image_data = handle.read() - -print(pictures) -print(image_data) - - -# Example 3 -pictures = {} -path = 'profile_9239.png' - -with open(path, 'wb') as f: - f.write(b'image data here 9239') - -try: - handle = pictures.setdefault(path, open(path, 'a+b')) -except OSError: - print(f'Failed to open path {path}') - raise -else: - handle.seek(0) - image_data = handle.read() - -print(pictures) -print(image_data) - - -# Example 4 -try: - path = 'profile_4555.csv' - - with open(path, 'wb') as f: - f.write(b'image data here 9239') - - from collections import defaultdict - - def open_picture(profile_path): - try: - return open(profile_path, 'a+b') - except OSError: - print(f'Failed to open path {profile_path}') - raise - - pictures = defaultdict(open_picture) - handle = pictures[path] - handle.seek(0) - image_data = handle.read() -except: - logging.exception('Expected') -else: - assert False - - -# Example 5 -path = 'account_9090.csv' - -with open(path, 'wb') as f: - f.write(b'image data here 9090') - -def open_picture(profile_path): - try: - return open(profile_path, 'a+b') - except OSError: - print(f'Failed to open path {profile_path}') - raise - -class Pictures(dict): - def __missing__(self, key): - value = open_picture(key) - self[key] = value - return value - -pictures = Pictures() -handle = pictures[path] -handle.seek(0) -image_data = handle.read() -print(pictures) -print(image_data) diff --git a/example_code/item_19.py b/example_code/item_19.py deleted file mode 100755 index 35f20c7..0000000 --- a/example_code/item_19.py +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -def get_stats(numbers): - minimum = min(numbers) - maximum = max(numbers) - return minimum, maximum - -lengths = [63, 73, 72, 60, 67, 66, 71, 61, 72, 70] - -minimum, maximum = get_stats(lengths) # Two return values - -print(f'Min: {minimum}, Max: {maximum}') - - -# Example 2 -first, second = 1, 2 -assert first == 1 -assert second == 2 - -def my_function(): - return 1, 2 - -first, second = my_function() -assert first == 1 -assert second == 2 - - -# Example 3 -def get_avg_ratio(numbers): - average = sum(numbers) / len(numbers) - scaled = [x / average for x in numbers] - scaled.sort(reverse=True) - return scaled - -longest, *middle, shortest = get_avg_ratio(lengths) - -print(f'Longest: {longest:>4.0%}') -print(f'Shortest: {shortest:>4.0%}') - - -# Example 4 -def get_stats(numbers): - minimum = min(numbers) - maximum = max(numbers) - count = len(numbers) - average = sum(numbers) / count - - sorted_numbers = sorted(numbers) - middle = count // 2 - if count % 2 == 0: - lower = sorted_numbers[middle - 1] - upper = sorted_numbers[middle] - median = (lower + upper) / 2 - else: - median = sorted_numbers[middle] - - return minimum, maximum, average, median, count - -minimum, maximum, average, median, count = get_stats(lengths) - -print(f'Min: {minimum}, Max: {maximum}') -print(f'Average: {average}, Median: {median}, Count {count}') - -assert minimum == 60 -assert maximum == 73 -assert average == 67.5 -assert median == 68.5 -assert count == 10 - -# Verify odd count median -_, _, _, median, count = get_stats([1, 2, 3]) -assert median == 2 -assert count == 3 - - -# Example 5 -# Correct: -minimum, maximum, average, median, count = get_stats(lengths) - -# Oops! Median and average swapped: -minimum, maximum, median, average, count = get_stats(lengths) - - -# Example 6 -minimum, maximum, average, median, count = get_stats( - lengths) - -minimum, maximum, average, median, count = \ - get_stats(lengths) - -(minimum, maximum, average, - median, count) = get_stats(lengths) - -(minimum, maximum, average, median, count - ) = get_stats(lengths) diff --git a/example_code/item_20.py b/example_code/item_20.py deleted file mode 100755 index 4a9639b..0000000 --- a/example_code/item_20.py +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -def careful_divide(a, b): - try: - return a / b - except ZeroDivisionError: - return None - -assert careful_divide(4, 2) == 2 -assert careful_divide(0, 1) == 0 -assert careful_divide(3, 6) == 0.5 -assert careful_divide(1, 0) == None - - -# Example 2 -x, y = 1, 0 -result = careful_divide(x, y) -if result is None: - print('Invalid inputs') -else: - print('Result is %.1f' % result) - - -# Example 3 -x, y = 0, 5 -result = careful_divide(x, y) -if not result: - print('Invalid inputs') # This runs! But shouldn't -else: - assert False - - -# Example 4 -def careful_divide(a, b): - try: - return True, a / b - except ZeroDivisionError: - return False, None - -assert careful_divide(4, 2) == (True, 2) -assert careful_divide(0, 1) == (True, 0) -assert careful_divide(3, 6) == (True, 0.5) -assert careful_divide(1, 0) == (False, None) - - -# Example 5 -x, y = 5, 0 -success, result = careful_divide(x, y) -if not success: - print('Invalid inputs') - - -# Example 6 -x, y = 5, 0 -_, result = careful_divide(x, y) -if not result: - print('Invalid inputs') - - -# Example 7 -def careful_divide(a, b): - try: - return a / b - except ZeroDivisionError as e: - raise ValueError('Invalid inputs') - - -# Example 8 -x, y = 5, 2 -try: - result = careful_divide(x, y) -except ValueError: - print('Invalid inputs') -else: - print('Result is %.1f' % result) - - -# Example 9 -def careful_divide(a: float, b: float) -> float: - """Divides a by b. - - Raises: - ValueError: When the inputs cannot be divided. - """ - try: - return a / b - except ZeroDivisionError as e: - raise ValueError('Invalid inputs') - -try: - result = careful_divide(1, 0) - assert False -except ValueError: - pass # Expected - -assert careful_divide(1, 5) == 0.2 diff --git a/example_code/item_21.py b/example_code/item_21.py deleted file mode 100755 index 10f97ae..0000000 --- a/example_code/item_21.py +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -def sort_priority(values, group): - def helper(x): - if x in group: - return (0, x) - return (1, x) - values.sort(key=helper) - - -# Example 2 -numbers = [8, 3, 1, 2, 5, 4, 7, 6] -group = {2, 3, 5, 7} -sort_priority(numbers, group) -print(numbers) - - -# Example 3 -def sort_priority2(numbers, group): - found = False - def helper(x): - if x in group: - found = True # Seems simple - return (0, x) - return (1, x) - numbers.sort(key=helper) - return found - - -# Example 4 -numbers = [8, 3, 1, 2, 5, 4, 7, 6] -found = sort_priority2(numbers, group) -print('Found:', found) -print(numbers) - - -# Example 5 -try: - foo = does_not_exist * 5 -except: - logging.exception('Expected') -else: - assert False - - -# Example 6 -def sort_priority2(numbers, group): - found = False # Scope: 'sort_priority2' - def helper(x): - if x in group: - found = True # Scope: 'helper' -- Bad! - return (0, x) - return (1, x) - numbers.sort(key=helper) - return found - - -# Example 7 -def sort_priority3(numbers, group): - found = False - def helper(x): - nonlocal found # Added - if x in group: - found = True - return (0, x) - return (1, x) - numbers.sort(key=helper) - return found - - -# Example 8 -numbers = [8, 3, 1, 2, 5, 4, 7, 6] -found = sort_priority3(numbers, group) -assert found -assert numbers == [2, 3, 5, 7, 1, 4, 6, 8] - - -# Example 9 -numbers = [8, 3, 1, 2, 5, 4, 7, 6] -class Sorter: - def __init__(self, group): - self.group = group - self.found = False - - def __call__(self, x): - if x in self.group: - self.found = True - return (0, x) - return (1, x) - -sorter = Sorter(group) -numbers.sort(key=sorter) -assert sorter.found is True -assert numbers == [2, 3, 5, 7, 1, 4, 6, 8] diff --git a/example_code/item_22.py b/example_code/item_22.py deleted file mode 100755 index 4b8abbd..0000000 --- a/example_code/item_22.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -def log(message, values): - if not values: - print(message) - else: - values_str = ', '.join(str(x) for x in values) - print(f'{message}: {values_str}') - -log('My numbers are', [1, 2]) -log('Hi there', []) - - -# Example 2 -def log(message, *values): # The only difference - if not values: - print(message) - else: - values_str = ', '.join(str(x) for x in values) - print(f'{message}: {values_str}') - -log('My numbers are', 1, 2) -log('Hi there') # Much better - - -# Example 3 -favorites = [7, 33, 99] -log('Favorite colors', *favorites) - - -# Example 4 -def my_generator(): - for i in range(10): - yield i - -def my_func(*args): - print(args) - -it = my_generator() -my_func(*it) - - -# Example 5 -def log(sequence, message, *values): - if not values: - print(f'{sequence} - {message}') - else: - values_str = ', '.join(str(x) for x in values) - print(f'{sequence} - {message}: {values_str}') - -log(1, 'Favorites', 7, 33) # New with *args OK -log(1, 'Hi there') # New message only OK -log('Favorite numbers', 7, 33) # Old usage breaks diff --git a/example_code/item_23.py b/example_code/item_23.py deleted file mode 100755 index 99c6fe8..0000000 --- a/example_code/item_23.py +++ /dev/null @@ -1,161 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -def remainder(number, divisor): - return number % divisor - -assert remainder(20, 7) == 6 - - -# Example 2 -remainder(20, 7) -remainder(20, divisor=7) -remainder(number=20, divisor=7) -remainder(divisor=7, number=20) - - -# Example 3 -try: - # This will not compile - source = """remainder(number=20, 7)""" - eval(source) -except: - logging.exception('Expected') -else: - assert False - - -# Example 4 -try: - remainder(20, number=7) -except: - logging.exception('Expected') -else: - assert False - - -# Example 5 -my_kwargs = { - 'number': 20, - 'divisor': 7, -} -assert remainder(**my_kwargs) == 6 - - -# Example 6 -my_kwargs = { - 'divisor': 7, -} -assert remainder(number=20, **my_kwargs) == 6 - - -# Example 7 -my_kwargs = { - 'number': 20, -} -other_kwargs = { - 'divisor': 7, -} -assert remainder(**my_kwargs, **other_kwargs) == 6 - - -# Example 8 -def print_parameters(**kwargs): - for key, value in kwargs.items(): - print(f'{key} = {value}') - -print_parameters(alpha=1.5, beta=9, gamma=4) - - -# Example 9 -def flow_rate(weight_diff, time_diff): - return weight_diff / time_diff - -weight_diff = 0.5 -time_diff = 3 -flow = flow_rate(weight_diff, time_diff) -print(f'{flow:.3} kg per second') - - -# Example 10 -def flow_rate(weight_diff, time_diff, period): - return (weight_diff / time_diff) * period - - -# Example 11 -flow_per_second = flow_rate(weight_diff, time_diff, 1) - - -# Example 12 -def flow_rate(weight_diff, time_diff, period=1): - return (weight_diff / time_diff) * period - - -# Example 13 -flow_per_second = flow_rate(weight_diff, time_diff) -flow_per_hour = flow_rate(weight_diff, time_diff, period=3600) -print(flow_per_second) -print(flow_per_hour) - - -# Example 14 -def flow_rate(weight_diff, time_diff, - period=1, units_per_kg=1): - return ((weight_diff * units_per_kg) / time_diff) * period - - -# Example 15 -pounds_per_hour = flow_rate(weight_diff, time_diff, - period=3600, units_per_kg=2.2) -print(pounds_per_hour) - - -# Example 16 -pounds_per_hour = flow_rate(weight_diff, time_diff, 3600, 2.2) -print(pounds_per_hour) diff --git a/example_code/item_24.py b/example_code/item_24.py deleted file mode 100755 index 124da9b..0000000 --- a/example_code/item_24.py +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -from time import sleep -from datetime import datetime - -def log(message, when=datetime.now()): - print(f'{when}: {message}') - -log('Hi there!') -sleep(0.1) -log('Hello again!') - - -# Example 2 -def log(message, when=None): - """Log a message with a timestamp. - - Args: - message: Message to print. - when: datetime of when the message occurred. - Defaults to the present time. - """ - if when is None: - when = datetime.now() - print(f'{when}: {message}') - - -# Example 3 -log('Hi there!') -sleep(0.1) -log('Hello again!') - - -# Example 4 -import json - -def decode(data, default={}): - try: - return json.loads(data) - except ValueError: - return default - - -# Example 5 -foo = decode('bad data') -foo['stuff'] = 5 -bar = decode('also bad') -bar['meep'] = 1 -print('Foo:', foo) -print('Bar:', bar) - - -# Example 6 -assert foo is bar - - -# Example 7 -def decode(data, default=None): - """Load JSON data from a string. - - Args: - data: JSON data to decode. - default: Value to return if decoding fails. - Defaults to an empty dictionary. - """ - try: - return json.loads(data) - except ValueError: - if default is None: - default = {} - return default - - -# Example 8 -foo = decode('bad data') -foo['stuff'] = 5 -bar = decode('also bad') -bar['meep'] = 1 -print('Foo:', foo) -print('Bar:', bar) -assert foo is not bar diff --git a/example_code/item_24_example_09.py b/example_code/item_24_example_09.py deleted file mode 100755 index 7cbaa71..0000000 --- a/example_code/item_24_example_09.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -# Example 9 -# Check types in this file with: python -m mypy - -from datetime import datetime -from time import sleep -from typing import Optional - -def log_typed(message: str, - when: Optional[datetime]=None) -> None: - """Log a message with a timestamp. - - Args: - message: Message to print. - when: datetime of when the message occurred. - Defaults to the present time. - """ - if when is None: - when = datetime.now() - print(f'{when}: {message}') - -log_typed('Hi there!') -sleep(0.1) -log_typed('Hello again!') diff --git a/example_code/item_25.py b/example_code/item_25.py deleted file mode 100755 index 2f1cce2..0000000 --- a/example_code/item_25.py +++ /dev/null @@ -1,238 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -def safe_division(number, divisor, - ignore_overflow, - ignore_zero_division): - try: - return number / divisor - except OverflowError: - if ignore_overflow: - return 0 - else: - raise - except ZeroDivisionError: - if ignore_zero_division: - return float('inf') - else: - raise - - -# Example 2 -result = safe_division(1.0, 10**500, True, False) -print(result) - - -# Example 3 -result = safe_division(1.0, 0, False, True) -print(result) - - -# Example 4 -def safe_division_b(number, divisor, - ignore_overflow=False, # Changed - ignore_zero_division=False): # Changed - try: - return number / divisor - except OverflowError: - if ignore_overflow: - return 0 - else: - raise - except ZeroDivisionError: - if ignore_zero_division: - return float('inf') - else: - raise - - -# Example 5 -result = safe_division_b(1.0, 10**500, ignore_overflow=True) -print(result) - -result = safe_division_b(1.0, 0, ignore_zero_division=True) -print(result) - - -# Example 6 -assert safe_division_b(1.0, 10**500, True, False) == 0 - - -# Example 7 -def safe_division_c(number, divisor, *, # Changed - ignore_overflow=False, - ignore_zero_division=False): - try: - return number / divisor - except OverflowError: - if ignore_overflow: - return 0 - else: - raise - except ZeroDivisionError: - if ignore_zero_division: - return float('inf') - else: - raise - - -# Example 8 -try: - safe_division_c(1.0, 10**500, True, False) -except: - logging.exception('Expected') -else: - assert False - - -# Example 9 -result = safe_division_c(1.0, 0, ignore_zero_division=True) -assert result == float('inf') - -try: - result = safe_division_c(1.0, 0) -except ZeroDivisionError: - pass # Expected -else: - assert False - - -# Example 10 -assert safe_division_c(number=2, divisor=5) == 0.4 -assert safe_division_c(divisor=5, number=2) == 0.4 -assert safe_division_c(2, divisor=5) == 0.4 - - -# Example 11 -def safe_division_c(numerator, denominator, *, # Changed - ignore_overflow=False, - ignore_zero_division=False): - try: - return numerator / denominator - except OverflowError: - if ignore_overflow: - return 0 - else: - raise - except ZeroDivisionError: - if ignore_zero_division: - return float('inf') - else: - raise - - -# Example 12 -try: - safe_division_c(number=2, divisor=5) -except: - logging.exception('Expected') -else: - assert False - - -# Example 13 -def safe_division_d(numerator, denominator, /, *, # Changed - ignore_overflow=False, - ignore_zero_division=False): - try: - return numerator / denominator - except OverflowError: - if ignore_overflow: - return 0 - else: - raise - except ZeroDivisionError: - if ignore_zero_division: - return float('inf') - else: - raise - - -# Example 14 -assert safe_division_d(2, 5) == 0.4 - - -# Example 15 -try: - safe_division_d(numerator=2, denominator=5) -except: - logging.exception('Expected') -else: - assert False - - -# Example 16 -def safe_division_e(numerator, denominator, /, - ndigits=10, *, # Changed - ignore_overflow=False, - ignore_zero_division=False): - try: - fraction = numerator / denominator # Changed - return round(fraction, ndigits) # Changed - except OverflowError: - if ignore_overflow: - return 0 - else: - raise - except ZeroDivisionError: - if ignore_zero_division: - return float('inf') - else: - raise - - -# Example 17 -result = safe_division_e(22, 7) -print(result) - -result = safe_division_e(22, 7, 5) -print(result) - -result = safe_division_e(22, 7, ndigits=2) -print(result) diff --git a/example_code/item_26.py b/example_code/item_26.py deleted file mode 100755 index 105b47b..0000000 --- a/example_code/item_26.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -def trace(func): - def wrapper(*args, **kwargs): - result = func(*args, **kwargs) - print(f'{func.__name__}({args!r}, {kwargs!r}) ' - f'-> {result!r}') - return result - return wrapper - - -# Example 2 -@trace -def fibonacci(n): - """Return the n-th Fibonacci number""" - if n in (0, 1): - return n - return (fibonacci(n - 2) + fibonacci(n - 1)) - - -# Example 3 -def fibonacci(n): - """Return the n-th Fibonacci number""" - if n in (0, 1): - return n - return fibonacci(n - 2) + fibonacci(n - 1) - -fibonacci = trace(fibonacci) - - -# Example 4 -fibonacci(4) - - -# Example 5 -print(fibonacci) - - -# Example 6 -help(fibonacci) - - -# Example 7 -try: - import pickle - - pickle.dumps(fibonacci) -except: - logging.exception('Expected') -else: - assert False - - -# Example 8 -from functools import wraps - -def trace(func): - @wraps(func) - def wrapper(*args, **kwargs): - result = func(*args, **kwargs) - print(f'{func.__name__}({args!r}, {kwargs!r}) ' - f'-> {result!r}') - return result - return wrapper - -@trace -def fibonacci(n): - """Return the n-th Fibonacci number""" - if n in (0, 1): - return n - return fibonacci(n - 2) + fibonacci(n - 1) - - -# Example 9 -help(fibonacci) - - -# Example 10 -print(pickle.dumps(fibonacci)) diff --git a/example_code/item_27.py b/example_code/item_27.py deleted file mode 100755 index c1a4847..0000000 --- a/example_code/item_27.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -squares = [] -for x in a: - squares.append(x**2) -print(squares) - - -# Example 2 -squares = [x**2 for x in a] # List comprehension -print(squares) - - -# Example 3 -alt = map(lambda x: x ** 2, a) -assert list(alt) == squares, f'{alt} {squares}' - - -# Example 4 -even_squares = [x**2 for x in a if x % 2 == 0] -print(even_squares) - - -# Example 5 -alt = map(lambda x: x**2, filter(lambda x: x % 2 == 0, a)) -assert even_squares == list(alt) - - -# Example 6 -even_squares_dict = {x: x**2 for x in a if x % 2 == 0} -threes_cubed_set = {x**3 for x in a if x % 3 == 0} -print(even_squares_dict) -print(threes_cubed_set) - - -# Example 7 -alt_dict = dict(map(lambda x: (x, x**2), - filter(lambda x: x % 2 == 0, a))) -alt_set = set(map(lambda x: x**3, - filter(lambda x: x % 3 == 0, a))) -assert even_squares_dict == alt_dict -assert threes_cubed_set == alt_set diff --git a/example_code/item_28.py b/example_code/item_28.py deleted file mode 100755 index d1efe24..0000000 --- a/example_code/item_28.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] -flat = [x for row in matrix for x in row] -print(flat) - - -# Example 2 -squared = [[x**2 for x in row] for row in matrix] -print(squared) - - -# Example 3 -my_lists = [ - [[1, 2, 3], [4, 5, 6]], - [[7, 8, 9], [10, 11, 12]], -] -flat = [x for sublist1 in my_lists - for sublist2 in sublist1 - for x in sublist2] -print(flat) - - -# Example 4 -flat = [] -for sublist1 in my_lists: - for sublist2 in sublist1: - flat.extend(sublist2) -print(flat) - - -# Example 5 -a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -b = [x for x in a if x > 4 if x % 2 == 0] -c = [x for x in a if x > 4 and x % 2 == 0] -print(b) -print(c) -assert b and c -assert b == c - - -# Example 6 -matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] -filtered = [[x for x in row if x % 3 == 0] - for row in matrix if sum(row) >= 10] -print(filtered) diff --git a/example_code/item_29.py b/example_code/item_29.py deleted file mode 100755 index 207faf1..0000000 --- a/example_code/item_29.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -stock = { - 'nails': 125, - 'screws': 35, - 'wingnuts': 8, - 'washers': 24, -} - -order = ['screws', 'wingnuts', 'clips'] - -def get_batches(count, size): - return count // size - -result = {} -for name in order: - count = stock.get(name, 0) - batches = get_batches(count, 8) - if batches: - result[name] = batches - -print(result) - - -# Example 2 -found = {name: get_batches(stock.get(name, 0), 8) - for name in order - if get_batches(stock.get(name, 0), 8)} -print(found) - - -# Example 3 -has_bug = {name: get_batches(stock.get(name, 0), 4) - for name in order - if get_batches(stock.get(name, 0), 8)} - -print('Expected:', found) -print('Found: ', has_bug) - - -# Example 4 -found = {name: batches for name in order - if (batches := get_batches(stock.get(name, 0), 8))} -assert found == {'screws': 4, 'wingnuts': 1}, found - - -# Example 5 -try: - result = {name: (tenth := count // 10) - for name, count in stock.items() if tenth > 0} -except: - logging.exception('Expected') -else: - assert False - - -# Example 6 -result = {name: tenth for name, count in stock.items() - if (tenth := count // 10) > 0} -print(result) - - -# Example 7 -half = [(last := count // 2) for count in stock.values()] -print(f'Last item of {half} is {last}') - - -# Example 8 -for count in stock.values(): # Leaks loop variable - pass -print(f'Last item of {list(stock.values())} is {count}') - - -# Example 9 -try: - del count - half = [count // 2 for count in stock.values()] - print(half) # Works - print(count) # Exception because loop variable didn't leak -except: - logging.exception('Expected') -else: - assert False - - -# Example 10 -found = ((name, batches) for name in order - if (batches := get_batches(stock.get(name, 0), 8))) -print(next(found)) -print(next(found)) diff --git a/example_code/item_30.py b/example_code/item_30.py deleted file mode 100755 index ba4fa9b..0000000 --- a/example_code/item_30.py +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -def index_words(text): - result = [] - if text: - result.append(0) - for index, letter in enumerate(text): - if letter == ' ': - result.append(index + 1) - return result - - -# Example 2 -address = 'Four score and seven years ago...' -address = 'Four score and seven years ago our fathers brought forth on this continent a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal.' -result = index_words(address) -print(result[:10]) - - -# Example 3 -def index_words_iter(text): - if text: - yield 0 - for index, letter in enumerate(text): - if letter == ' ': - yield index + 1 - - -# Example 4 -it = index_words_iter(address) -print(next(it)) -print(next(it)) - - -# Example 5 -result = list(index_words_iter(address)) -print(result[:10]) - - -# Example 6 -def index_file(handle): - offset = 0 - for line in handle: - if line: - yield offset - for letter in line: - offset += 1 - if letter == ' ': - yield offset - - -# Example 7 -address_lines = """Four score and seven years -ago our fathers brought forth on this -continent a new nation, conceived in liberty, -and dedicated to the proposition that all men -are created equal.""" - -with open('address.txt', 'w') as f: - f.write(address_lines) - -import itertools -with open('address.txt', 'r') as f: - it = index_file(f) - results = itertools.islice(it, 0, 10) - print(list(results)) diff --git a/example_code/item_31.py b/example_code/item_31.py deleted file mode 100755 index 122d480..0000000 --- a/example_code/item_31.py +++ /dev/null @@ -1,209 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -def normalize(numbers): - total = sum(numbers) - result = [] - for value in numbers: - percent = 100 * value / total - result.append(percent) - return result - - -# Example 2 -visits = [15, 35, 80] -percentages = normalize(visits) -print(percentages) -assert sum(percentages) == 100.0 - - -# Example 3 -path = 'my_numbers.txt' -with open(path, 'w') as f: - for i in (15, 35, 80): - f.write('%d\n' % i) - -def read_visits(data_path): - with open(data_path) as f: - for line in f: - yield int(line) - - -# Example 4 -it = read_visits('my_numbers.txt') -percentages = normalize(it) -print(percentages) - - -# Example 5 -it = read_visits('my_numbers.txt') -print(list(it)) -print(list(it)) # Already exhausted - - -# Example 6 -def normalize_copy(numbers): - numbers_copy = list(numbers) # Copy the iterator - total = sum(numbers_copy) - result = [] - for value in numbers_copy: - percent = 100 * value / total - result.append(percent) - return result - - -# Example 7 -it = read_visits('my_numbers.txt') -percentages = normalize_copy(it) -print(percentages) -assert sum(percentages) == 100.0 - - -# Example 8 -def normalize_func(get_iter): - total = sum(get_iter()) # New iterator - result = [] - for value in get_iter(): # New iterator - percent = 100 * value / total - result.append(percent) - return result - - -# Example 9 -path = 'my_numbers.txt' -percentages = normalize_func(lambda: read_visits(path)) -print(percentages) -assert sum(percentages) == 100.0 - - -# Example 10 -class ReadVisits: - def __init__(self, data_path): - self.data_path = data_path - - def __iter__(self): - with open(self.data_path) as f: - for line in f: - yield int(line) - - -# Example 11 -visits = ReadVisits(path) -percentages = normalize(visits) -print(percentages) -assert sum(percentages) == 100.0 - - -# Example 12 -def normalize_defensive(numbers): - if iter(numbers) is numbers: # An iterator -- bad! - raise TypeError('Must supply a container') - total = sum(numbers) - result = [] - for value in numbers: - percent = 100 * value / total - result.append(percent) - return result - -visits = [15, 35, 80] -normalize_defensive(visits) # No error - -it = iter(visits) -try: - normalize_defensive(it) -except TypeError: - pass -else: - assert False - - -# Example 13 -from collections.abc import Iterator - -def normalize_defensive(numbers): - if isinstance(numbers, Iterator): # Another way to check - raise TypeError('Must supply a container') - total = sum(numbers) - result = [] - for value in numbers: - percent = 100 * value / total - result.append(percent) - return result - -visits = [15, 35, 80] -normalize_defensive(visits) # No error - -it = iter(visits) -try: - normalize_defensive(it) -except TypeError: - pass -else: - assert False - - -# Example 14 -visits = [15, 35, 80] -percentages = normalize_defensive(visits) -assert sum(percentages) == 100.0 - -visits = ReadVisits(path) -percentages = normalize_defensive(visits) -assert sum(percentages) == 100.0 - - -# Example 15 -try: - visits = [15, 35, 80] - it = iter(visits) - normalize_defensive(it) -except: - logging.exception('Expected') -else: - assert False diff --git a/example_code/item_32.py b/example_code/item_32.py deleted file mode 100755 index 1cefe9b..0000000 --- a/example_code/item_32.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -import random - -with open('my_file.txt', 'w') as f: - for _ in range(10): - f.write('a' * random.randint(0, 100)) - f.write('\n') - -value = [len(x) for x in open('my_file.txt')] -print(value) - - -# Example 2 -it = (len(x) for x in open('my_file.txt')) -print(it) - - -# Example 3 -print(next(it)) -print(next(it)) - - -# Example 4 -roots = ((x, x**0.5) for x in it) - - -# Example 5 -print(next(roots)) diff --git a/example_code/item_33.py b/example_code/item_33.py deleted file mode 100755 index d7bd5c6..0000000 --- a/example_code/item_33.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -def move(period, speed): - for _ in range(period): - yield speed - -def pause(delay): - for _ in range(delay): - yield 0 - - -# Example 2 -def animate(): - for delta in move(4, 5.0): - yield delta - for delta in pause(3): - yield delta - for delta in move(2, 3.0): - yield delta - - -# Example 3 -def render(delta): - print(f'Delta: {delta:.1f}') - # Move the images onscreen - -def run(func): - for delta in func(): - render(delta) - -run(animate) - - -# Example 4 -def animate_composed(): - yield from move(4, 5.0) - yield from pause(3) - yield from move(2, 3.0) - -run(animate_composed) - - -# Example 5 -import timeit - -def child(): - for i in range(1_000_000): - yield i - -def slow(): - for i in child(): - yield i - -def fast(): - yield from child() - -baseline = timeit.timeit( - stmt='for _ in slow(): pass', - globals=globals(), - number=50) -print(f'Manual nesting {baseline:.2f}s') - -comparison = timeit.timeit( - stmt='for _ in fast(): pass', - globals=globals(), - number=50) -print(f'Composed nesting {comparison:.2f}s') - -reduction = -(comparison - baseline) / baseline -print(f'{reduction:.1%} less time') diff --git a/example_code/item_34.py b/example_code/item_34.py deleted file mode 100755 index 6a82a35..0000000 --- a/example_code/item_34.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -import math - -def wave(amplitude, steps): - step_size = 2 * math.pi / steps - for step in range(steps): - radians = step * step_size - fraction = math.sin(radians) - output = amplitude * fraction - yield output - - -# Example 2 -def transmit(output): - if output is None: - print(f'Output is None') - else: - print(f'Output: {output:>5.1f}') - -def run(it): - for output in it: - transmit(output) - -run(wave(3.0, 8)) - - -# Example 3 -def my_generator(): - received = yield 1 - print(f'received = {received}') - -it = my_generator() -output = next(it) # Get first generator output -print(f'output = {output}') - -try: - next(it) # Run generator until it exits -except StopIteration: - pass -else: - assert False - - -# Example 4 -it = my_generator() -output = it.send(None) # Get first generator output -print(f'output = {output}') - -try: - it.send('hello!') # Send value into the generator -except StopIteration: - pass -else: - assert False - - -# Example 5 -def wave_modulating(steps): - step_size = 2 * math.pi / steps - amplitude = yield # Receive initial amplitude - for step in range(steps): - radians = step * step_size - fraction = math.sin(radians) - output = amplitude * fraction - amplitude = yield output # Receive next amplitude - - -# Example 6 -def run_modulating(it): - amplitudes = [ - None, 7, 7, 7, 2, 2, 2, 2, 10, 10, 10, 10, 10] - for amplitude in amplitudes: - output = it.send(amplitude) - transmit(output) - -run_modulating(wave_modulating(12)) - - -# Example 7 -def complex_wave(): - yield from wave(7.0, 3) - yield from wave(2.0, 4) - yield from wave(10.0, 5) - -run(complex_wave()) - - -# Example 8 -def complex_wave_modulating(): - yield from wave_modulating(3) - yield from wave_modulating(4) - yield from wave_modulating(5) - -run_modulating(complex_wave_modulating()) - - -# Example 9 -def wave_cascading(amplitude_it, steps): - step_size = 2 * math.pi / steps - for step in range(steps): - radians = step * step_size - fraction = math.sin(radians) - amplitude = next(amplitude_it) # Get next input - output = amplitude * fraction - yield output - - -# Example 10 -def complex_wave_cascading(amplitude_it): - yield from wave_cascading(amplitude_it, 3) - yield from wave_cascading(amplitude_it, 4) - yield from wave_cascading(amplitude_it, 5) - - -# Example 11 -def run_cascading(): - amplitudes = [7, 7, 7, 2, 2, 2, 2, 10, 10, 10, 10, 10] - it = complex_wave_cascading(iter(amplitudes)) - for amplitude in amplitudes: - output = next(it) - transmit(output) - -run_cascading() diff --git a/example_code/item_35.py b/example_code/item_35.py deleted file mode 100755 index 1d53234..0000000 --- a/example_code/item_35.py +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -try: - class MyError(Exception): - pass - - def my_generator(): - yield 1 - yield 2 - yield 3 - - it = my_generator() - print(next(it)) # Yield 1 - print(next(it)) # Yield 2 - print(it.throw(MyError('test error'))) -except: - logging.exception('Expected') -else: - assert False - - -# Example 2 -def my_generator(): - yield 1 - - try: - yield 2 - except MyError: - print('Got MyError!') - else: - yield 3 - - yield 4 - -it = my_generator() -print(next(it)) # Yield 1 -print(next(it)) # Yield 2 -print(it.throw(MyError('test error'))) - - -# Example 3 -class Reset(Exception): - pass - -def timer(period): - current = period - while current: - current -= 1 - try: - yield current - except Reset: - current = period - - -# Example 4 -RESETS = [ - False, False, False, True, False, True, False, - False, False, False, False, False, False, False] - -def check_for_reset(): - # Poll for external event - return RESETS.pop(0) - -def announce(remaining): - print(f'{remaining} ticks remaining') - -def run(): - it = timer(4) - while True: - try: - if check_for_reset(): - current = it.throw(Reset()) - else: - current = next(it) - except StopIteration: - break - else: - announce(current) - -run() - - -# Example 5 -class Timer: - def __init__(self, period): - self.current = period - self.period = period - - def reset(self): - self.current = self.period - - def __iter__(self): - while self.current: - self.current -= 1 - yield self.current - - -# Example 6 -RESETS = [ - False, False, True, False, True, False, - False, False, False, False, False, False, False] - -def run(): - timer = Timer(4) - for current in timer: - if check_for_reset(): - timer.reset() - announce(current) - -run() diff --git a/example_code/item_36.py b/example_code/item_36.py deleted file mode 100755 index 5829639..0000000 --- a/example_code/item_36.py +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -import itertools - - -# Example 2 -it = itertools.chain([1, 2, 3], [4, 5, 6]) -print(list(it)) - - -# Example 3 -it = itertools.repeat('hello', 3) -print(list(it)) - - -# Example 4 -it = itertools.cycle([1, 2]) -result = [next(it) for _ in range (10)] -print(result) - - -# Example 5 -it1, it2, it3 = itertools.tee(['first', 'second'], 3) -print(list(it1)) -print(list(it2)) -print(list(it3)) - - -# Example 6 -keys = ['one', 'two', 'three'] -values = [1, 2] - -normal = list(zip(keys, values)) -print('zip: ', normal) - -it = itertools.zip_longest(keys, values, fillvalue='nope') -longest = list(it) -print('zip_longest:', longest) - - -# Example 7 -values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - -first_five = itertools.islice(values, 5) -print('First five: ', list(first_five)) - -middle_odds = itertools.islice(values, 2, 8, 2) -print('Middle odds:', list(middle_odds)) - - -# Example 8 -values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -less_than_seven = lambda x: x < 7 -it = itertools.takewhile(less_than_seven, values) -print(list(it)) - - -# Example 9 -values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -less_than_seven = lambda x: x < 7 -it = itertools.dropwhile(less_than_seven, values) -print(list(it)) - - -# Example 10 -values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -evens = lambda x: x % 2 == 0 - -filter_result = filter(evens, values) -print('Filter: ', list(filter_result)) - -filter_false_result = itertools.filterfalse(evens, values) -print('Filter false:', list(filter_false_result)) - - -# Example 11 -values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -sum_reduce = itertools.accumulate(values) -print('Sum: ', list(sum_reduce)) - -def sum_modulo_20(first, second): - output = first + second - return output % 20 - -modulo_reduce = itertools.accumulate(values, sum_modulo_20) -print('Modulo:', list(modulo_reduce)) - - -# Example 12 -single = itertools.product([1, 2], repeat=2) -print('Single: ', list(single)) - -multiple = itertools.product([1, 2], ['a', 'b']) -print('Multiple:', list(multiple)) - - -# Example 13 -it = itertools.permutations([1, 2, 3, 4], 2) -original_print = print -print = pprint -print(list(it)) -print = original_print - - -# Example 14 -it = itertools.combinations([1, 2, 3, 4], 2) -print(list(it)) - - -# Example 15 -it = itertools.combinations_with_replacement([1, 2, 3, 4], 2) -original_print = print -print = pprint -print(list(it)) -print = original_print diff --git a/example_code/item_37.py b/example_code/item_37.py deleted file mode 100755 index aa7585a..0000000 --- a/example_code/item_37.py +++ /dev/null @@ -1,230 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class SimpleGradebook: - def __init__(self): - self._grades = {} - - def add_student(self, name): - self._grades[name] = [] - - def report_grade(self, name, score): - self._grades[name].append(score) - - def average_grade(self, name): - grades = self._grades[name] - return sum(grades) / len(grades) - - -# Example 2 -book = SimpleGradebook() -book.add_student('Isaac Newton') -book.report_grade('Isaac Newton', 90) -book.report_grade('Isaac Newton', 95) -book.report_grade('Isaac Newton', 85) - -print(book.average_grade('Isaac Newton')) - - -# Example 3 -from collections import defaultdict - -class BySubjectGradebook: - def __init__(self): - self._grades = {} # Outer dict - - def add_student(self, name): - self._grades[name] = defaultdict(list) # Inner dict - - -# Example 4 - def report_grade(self, name, subject, grade): - by_subject = self._grades[name] - grade_list = by_subject[subject] - grade_list.append(grade) - - def average_grade(self, name): - by_subject = self._grades[name] - total, count = 0, 0 - for grades in by_subject.values(): - total += sum(grades) - count += len(grades) - return total / count - - -# Example 5 -book = BySubjectGradebook() -book.add_student('Albert Einstein') -book.report_grade('Albert Einstein', 'Math', 75) -book.report_grade('Albert Einstein', 'Math', 65) -book.report_grade('Albert Einstein', 'Gym', 90) -book.report_grade('Albert Einstein', 'Gym', 95) -print(book.average_grade('Albert Einstein')) - - -# Example 6 -class WeightedGradebook: - def __init__(self): - self._grades = {} - - def add_student(self, name): - self._grades[name] = defaultdict(list) - - def report_grade(self, name, subject, score, weight): - by_subject = self._grades[name] - grade_list = by_subject[subject] - grade_list.append((score, weight)) - - -# Example 7 - def average_grade(self, name): - by_subject = self._grades[name] - - score_sum, score_count = 0, 0 - for subject, scores in by_subject.items(): - subject_avg, total_weight = 0, 0 - for score, weight in scores: - subject_avg += score * weight - total_weight += weight - - score_sum += subject_avg / total_weight - score_count += 1 - - return score_sum / score_count - - -# Example 8 -book = WeightedGradebook() -book.add_student('Albert Einstein') -book.report_grade('Albert Einstein', 'Math', 75, 0.05) -book.report_grade('Albert Einstein', 'Math', 65, 0.15) -book.report_grade('Albert Einstein', 'Math', 70, 0.80) -book.report_grade('Albert Einstein', 'Gym', 100, 0.40) -book.report_grade('Albert Einstein', 'Gym', 85, 0.60) -print(book.average_grade('Albert Einstein')) - - -# Example 9 -grades = [] -grades.append((95, 0.45)) -grades.append((85, 0.55)) -total = sum(score * weight for score, weight in grades) -total_weight = sum(weight for _, weight in grades) -average_grade = total / total_weight -print(average_grade) - - -# Example 10 -grades = [] -grades.append((95, 0.45, 'Great job')) -grades.append((85, 0.55, 'Better next time')) -total = sum(score * weight for score, weight, _ in grades) -total_weight = sum(weight for _, weight, _ in grades) -average_grade = total / total_weight -print(average_grade) - - -# Example 11 -from collections import namedtuple - -Grade = namedtuple('Grade', ('score', 'weight')) - - -# Example 12 -class Subject: - def __init__(self): - self._grades = [] - - def report_grade(self, score, weight): - self._grades.append(Grade(score, weight)) - - def average_grade(self): - total, total_weight = 0, 0 - for grade in self._grades: - total += grade.score * grade.weight - total_weight += grade.weight - return total / total_weight - - -# Example 13 -class Student: - def __init__(self): - self._subjects = defaultdict(Subject) - - def get_subject(self, name): - return self._subjects[name] - - def average_grade(self): - total, count = 0, 0 - for subject in self._subjects.values(): - total += subject.average_grade() - count += 1 - return total / count - - -# Example 14 -class Gradebook: - def __init__(self): - self._students = defaultdict(Student) - - def get_student(self, name): - return self._students[name] - - -# Example 15 -book = Gradebook() -albert = book.get_student('Albert Einstein') -math = albert.get_subject('Math') -math.report_grade(75, 0.05) -math.report_grade(65, 0.15) -math.report_grade(70, 0.80) -gym = albert.get_subject('Gym') -gym.report_grade(100, 0.40) -gym.report_grade(85, 0.60) -print(albert.average_grade()) diff --git a/example_code/item_38.py b/example_code/item_38.py deleted file mode 100755 index f64d30a..0000000 --- a/example_code/item_38.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -names = ['Socrates', 'Archimedes', 'Plato', 'Aristotle'] -names.sort(key=len) -print(names) - - -# Example 2 -def log_missing(): - print('Key added') - return 0 - - -# Example 3 -from collections import defaultdict - -current = {'green': 12, 'blue': 3} -increments = [ - ('red', 5), - ('blue', 17), - ('orange', 9), -] -result = defaultdict(log_missing, current) -print('Before:', dict(result)) -for key, amount in increments: - result[key] += amount -print('After: ', dict(result)) - - -# Example 4 -def increment_with_report(current, increments): - added_count = 0 - - def missing(): - nonlocal added_count # Stateful closure - added_count += 1 - return 0 - - result = defaultdict(missing, current) - for key, amount in increments: - result[key] += amount - - return result, added_count - - -# Example 5 -result, count = increment_with_report(current, increments) -assert count == 2 -print(result) - - -# Example 6 -class CountMissing: - def __init__(self): - self.added = 0 - - def missing(self): - self.added += 1 - return 0 - - -# Example 7 -counter = CountMissing() -result = defaultdict(counter.missing, current) # Method ref -for key, amount in increments: - result[key] += amount -assert counter.added == 2 -print(result) - - -# Example 8 -class BetterCountMissing: - def __init__(self): - self.added = 0 - - def __call__(self): - self.added += 1 - return 0 - -counter = BetterCountMissing() -assert counter() == 0 -assert callable(counter) - - -# Example 9 -counter = BetterCountMissing() -result = defaultdict(counter, current) # Relies on __call__ -for key, amount in increments: - result[key] += amount -assert counter.added == 2 -print(result) diff --git a/example_code/item_39.py b/example_code/item_39.py deleted file mode 100755 index 60ae575..0000000 --- a/example_code/item_39.py +++ /dev/null @@ -1,209 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class InputData: - def read(self): - raise NotImplementedError - - -# Example 2 -class PathInputData(InputData): - def __init__(self, path): - super().__init__() - self.path = path - - def read(self): - with open(self.path) as f: - return f.read() - - -# Example 3 -class Worker: - def __init__(self, input_data): - self.input_data = input_data - self.result = None - - def map(self): - raise NotImplementedError - - def reduce(self, other): - raise NotImplementedError - - -# Example 4 -class LineCountWorker(Worker): - def map(self): - data = self.input_data.read() - self.result = data.count('\n') - - def reduce(self, other): - self.result += other.result - - -# Example 5 -import os - -def generate_inputs(data_dir): - for name in os.listdir(data_dir): - yield PathInputData(os.path.join(data_dir, name)) - - -# Example 6 -def create_workers(input_list): - workers = [] - for input_data in input_list: - workers.append(LineCountWorker(input_data)) - return workers - - -# Example 7 -from threading import Thread - -def execute(workers): - threads = [Thread(target=w.map) for w in workers] - for thread in threads: thread.start() - for thread in threads: thread.join() - - first, *rest = workers - for worker in rest: - first.reduce(worker) - return first.result - - -# Example 8 -def mapreduce(data_dir): - inputs = generate_inputs(data_dir) - workers = create_workers(inputs) - return execute(workers) - - -# Example 9 -import os -import random - -def write_test_files(tmpdir): - os.makedirs(tmpdir) - for i in range(100): - with open(os.path.join(tmpdir, str(i)), 'w') as f: - f.write('\n' * random.randint(0, 100)) - -tmpdir = 'test_inputs' -write_test_files(tmpdir) - -result = mapreduce(tmpdir) -print(f'There are {result} lines') - - -# Example 10 -class GenericInputData: - def read(self): - raise NotImplementedError - - @classmethod - def generate_inputs(cls, config): - raise NotImplementedError - - -# Example 11 -class PathInputData(GenericInputData): - def __init__(self, path): - super().__init__() - self.path = path - - def read(self): - with open(self.path) as f: - return f.read() - - @classmethod - def generate_inputs(cls, config): - data_dir = config['data_dir'] - for name in os.listdir(data_dir): - yield cls(os.path.join(data_dir, name)) - - -# Example 12 -class GenericWorker: - def __init__(self, input_data): - self.input_data = input_data - self.result = None - - def map(self): - raise NotImplementedError - - def reduce(self, other): - raise NotImplementedError - - @classmethod - def create_workers(cls, input_class, config): - workers = [] - for input_data in input_class.generate_inputs(config): - workers.append(cls(input_data)) - return workers - - -# Example 13 -class LineCountWorker(GenericWorker): - def map(self): - data = self.input_data.read() - self.result = data.count('\n') - - def reduce(self, other): - self.result += other.result - - -# Example 14 -def mapreduce(worker_class, input_class, config): - workers = worker_class.create_workers(input_class, config) - return execute(workers) - - -# Example 15 -config = {'data_dir': tmpdir} -result = mapreduce(LineCountWorker, PathInputData, config) -print(f'There are {result} lines') diff --git a/example_code/item_40.py b/example_code/item_40.py deleted file mode 100755 index 136eeb0..0000000 --- a/example_code/item_40.py +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class MyBaseClass: - def __init__(self, value): - self.value = value - -class MyChildClass(MyBaseClass): - def __init__(self): - MyBaseClass.__init__(self, 5) - - def times_two(self): - return self.value * 2 - -foo = MyChildClass() -assert foo.times_two() == 10 - - -# Example 2 -class TimesTwo: - def __init__(self): - self.value *= 2 - -class PlusFive: - def __init__(self): - self.value += 5 - - -# Example 3 -class OneWay(MyBaseClass, TimesTwo, PlusFive): - def __init__(self, value): - MyBaseClass.__init__(self, value) - TimesTwo.__init__(self) - PlusFive.__init__(self) - - -# Example 4 -foo = OneWay(5) -print('First ordering value is (5 * 2) + 5 =', foo.value) - - -# Example 5 -class AnotherWay(MyBaseClass, PlusFive, TimesTwo): - def __init__(self, value): - MyBaseClass.__init__(self, value) - TimesTwo.__init__(self) - PlusFive.__init__(self) - - -# Example 6 -bar = AnotherWay(5) -print('Second ordering value is', bar.value) - - -# Example 7 -class TimesSeven(MyBaseClass): - def __init__(self, value): - MyBaseClass.__init__(self, value) - self.value *= 7 - -class PlusNine(MyBaseClass): - def __init__(self, value): - MyBaseClass.__init__(self, value) - self.value += 9 - - -# Example 8 -class ThisWay(TimesSeven, PlusNine): - def __init__(self, value): - TimesSeven.__init__(self, value) - PlusNine.__init__(self, value) - -foo = ThisWay(5) -print('Should be (5 * 7) + 9 = 44 but is', foo.value) - - -# Example 9 -class MyBaseClass: - def __init__(self, value): - self.value = value - -class TimesSevenCorrect(MyBaseClass): - def __init__(self, value): - super().__init__(value) - self.value *= 7 - -class PlusNineCorrect(MyBaseClass): - def __init__(self, value): - super().__init__(value) - self.value += 9 - - -# Example 10 -class GoodWay(TimesSevenCorrect, PlusNineCorrect): - def __init__(self, value): - super().__init__(value) - -foo = GoodWay(5) -print('Should be 7 * (5 + 9) = 98 and is', foo.value) - - -# Example 11 -mro_str = '\n'.join(repr(cls) for cls in GoodWay.mro()) -print(mro_str) - - -# Example 12 -class ExplicitTrisect(MyBaseClass): - def __init__(self, value): - super(ExplicitTrisect, self).__init__(value) - self.value /= 3 -assert ExplicitTrisect(9).value == 3 - - -# Example 13 -class AutomaticTrisect(MyBaseClass): - def __init__(self, value): - super(__class__, self).__init__(value) - self.value /= 3 - -class ImplicitTrisect(MyBaseClass): - def __init__(self, value): - super().__init__(value) - self.value /= 3 - -assert ExplicitTrisect(9).value == 3 -assert AutomaticTrisect(9).value == 3 -assert ImplicitTrisect(9).value == 3 diff --git a/example_code/item_41.py b/example_code/item_41.py deleted file mode 100755 index ce8bfab..0000000 --- a/example_code/item_41.py +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class ToDictMixin: - def to_dict(self): - return self._traverse_dict(self.__dict__) - - -# Example 2 - def _traverse_dict(self, instance_dict): - output = {} - for key, value in instance_dict.items(): - output[key] = self._traverse(key, value) - return output - - def _traverse(self, key, value): - if isinstance(value, ToDictMixin): - return value.to_dict() - elif isinstance(value, dict): - return self._traverse_dict(value) - elif isinstance(value, list): - return [self._traverse(key, i) for i in value] - elif hasattr(value, '__dict__'): - return self._traverse_dict(value.__dict__) - else: - return value - - -# Example 3 -class BinaryTree(ToDictMixin): - def __init__(self, value, left=None, right=None): - self.value = value - self.left = left - self.right = right - - -# Example 4 -tree = BinaryTree(10, - left=BinaryTree(7, right=BinaryTree(9)), - right=BinaryTree(13, left=BinaryTree(11))) -orig_print = print -print = pprint -print(tree.to_dict()) -print = orig_print - - -# Example 5 -class BinaryTreeWithParent(BinaryTree): - def __init__(self, value, left=None, - right=None, parent=None): - super().__init__(value, left=left, right=right) - self.parent = parent - - -# Example 6 - def _traverse(self, key, value): - if (isinstance(value, BinaryTreeWithParent) and - key == 'parent'): - return value.value # Prevent cycles - else: - return super()._traverse(key, value) - - -# Example 7 -root = BinaryTreeWithParent(10) -root.left = BinaryTreeWithParent(7, parent=root) -root.left.right = BinaryTreeWithParent(9, parent=root.left) -orig_print = print -print = pprint -print(root.to_dict()) -print = orig_print - - -# Example 8 -class NamedSubTree(ToDictMixin): - def __init__(self, name, tree_with_parent): - self.name = name - self.tree_with_parent = tree_with_parent - -my_tree = NamedSubTree('foobar', root.left.right) -orig_print = print -print = pprint -print(my_tree.to_dict()) # No infinite loop -print = orig_print - - -# Example 9 -import json - -class JsonMixin: - @classmethod - def from_json(cls, data): - kwargs = json.loads(data) - return cls(**kwargs) - - def to_json(self): - return json.dumps(self.to_dict()) - - -# Example 10 -class DatacenterRack(ToDictMixin, JsonMixin): - def __init__(self, switch=None, machines=None): - self.switch = Switch(**switch) - self.machines = [ - Machine(**kwargs) for kwargs in machines] - -class Switch(ToDictMixin, JsonMixin): - def __init__(self, ports=None, speed=None): - self.ports = ports - self.speed = speed - -class Machine(ToDictMixin, JsonMixin): - def __init__(self, cores=None, ram=None, disk=None): - self.cores = cores - self.ram = ram - self.disk = disk - - -# Example 11 -serialized = """{ - "switch": {"ports": 5, "speed": 1e9}, - "machines": [ - {"cores": 8, "ram": 32e9, "disk": 5e12}, - {"cores": 4, "ram": 16e9, "disk": 1e12}, - {"cores": 2, "ram": 4e9, "disk": 500e9} - ] -}""" - -deserialized = DatacenterRack.from_json(serialized) -roundtrip = deserialized.to_json() -assert json.loads(serialized) == json.loads(roundtrip) diff --git a/example_code/item_42.py b/example_code/item_42.py deleted file mode 100755 index f6dd9fd..0000000 --- a/example_code/item_42.py +++ /dev/null @@ -1,214 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class MyObject: - def __init__(self): - self.public_field = 5 - self.__private_field = 10 - - def get_private_field(self): - return self.__private_field - - -# Example 2 -foo = MyObject() -assert foo.public_field == 5 - - -# Example 3 -assert foo.get_private_field() == 10 - - -# Example 4 -try: - foo.__private_field -except: - logging.exception('Expected') -else: - assert False - - -# Example 5 -class MyOtherObject: - def __init__(self): - self.__private_field = 71 - - @classmethod - def get_private_field_of_instance(cls, instance): - return instance.__private_field - -bar = MyOtherObject() -assert MyOtherObject.get_private_field_of_instance(bar) == 71 - - -# Example 6 -try: - class MyParentObject: - def __init__(self): - self.__private_field = 71 - - class MyChildObject(MyParentObject): - def get_private_field(self): - return self.__private_field - - baz = MyChildObject() - baz.get_private_field() -except: - logging.exception('Expected') -else: - assert False - - -# Example 7 -assert baz._MyParentObject__private_field == 71 - - -# Example 8 -print(baz.__dict__) - - -# Example 9 -class MyStringClass: - def __init__(self, value): - self.__value = value - - def get_value(self): - return str(self.__value) - -foo = MyStringClass(5) -assert foo.get_value() == '5' - - -# Example 10 -class MyIntegerSubclass(MyStringClass): - def get_value(self): - return int(self._MyStringClass__value) - -foo = MyIntegerSubclass('5') -assert foo.get_value() == 5 - - -# Example 11 -class MyBaseClass: - def __init__(self, value): - self.__value = value - - def get_value(self): - return self.__value - -class MyStringClass(MyBaseClass): - def get_value(self): - return str(super().get_value()) # Updated - -class MyIntegerSubclass(MyStringClass): - def get_value(self): - return int(self._MyStringClass__value) # Not updated - - -# Example 12 -try: - foo = MyIntegerSubclass(5) - foo.get_value() -except: - logging.exception('Expected') -else: - assert False - - -# Example 13 -class MyStringClass: - def __init__(self, value): - # This stores the user-supplied value for the object. - # It should be coercible to a string. Once assigned in - # the object it should be treated as immutable. - self._value = value - - - def get_value(self): - return str(self._value) -class MyIntegerSubclass(MyStringClass): - def get_value(self): - return self._value - -foo = MyIntegerSubclass(5) -assert foo.get_value() == 5 - - -# Example 14 -class ApiClass: - def __init__(self): - self._value = 5 - - def get(self): - return self._value - -class Child(ApiClass): - def __init__(self): - super().__init__() - self._value = 'hello' # Conflicts - -a = Child() -print(f'{a.get()} and {a._value} should be different') - - -# Example 15 -class ApiClass: - def __init__(self): - self.__value = 5 # Double underscore - - def get(self): - return self.__value # Double underscore - -class Child(ApiClass): - def __init__(self): - super().__init__() - self._value = 'hello' # OK! - -a = Child() -print(f'{a.get()} and {a._value} are different') diff --git a/example_code/item_43.py b/example_code/item_43.py deleted file mode 100755 index 5a842ac..0000000 --- a/example_code/item_43.py +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class FrequencyList(list): - def __init__(self, members): - super().__init__(members) - - def frequency(self): - counts = {} - for item in self: - counts[item] = counts.get(item, 0) + 1 - return counts - - -# Example 2 -foo = FrequencyList(['a', 'b', 'a', 'c', 'b', 'a', 'd']) -print('Length is', len(foo)) -foo.pop() -print('After pop:', repr(foo)) -print('Frequency:', foo.frequency()) - - -# Example 3 -class BinaryNode: - def __init__(self, value, left=None, right=None): - self.value = value - self.left = left - self.right = right - - -# Example 4 -bar = [1, 2, 3] -bar[0] - - -# Example 5 -bar.__getitem__(0) - - -# Example 6 -class IndexableNode(BinaryNode): - def _traverse(self): - if self.left is not None: - yield from self.left._traverse() - yield self - if self.right is not None: - yield from self.right._traverse() - - def __getitem__(self, index): - for i, item in enumerate(self._traverse()): - if i == index: - return item.value - raise IndexError(f'Index {index} is out of range') - - -# Example 7 -tree = IndexableNode( - 10, - left=IndexableNode( - 5, - left=IndexableNode(2), - right=IndexableNode( - 6, - right=IndexableNode(7))), - right=IndexableNode( - 15, - left=IndexableNode(11))) - - -# Example 8 -print('LRR is', tree.left.right.right.value) -print('Index 0 is', tree[0]) -print('Index 1 is', tree[1]) -print('11 in the tree?', 11 in tree) -print('17 in the tree?', 17 in tree) -print('Tree is', list(tree)) - -try: - tree[100] -except IndexError: - pass -else: - assert False - - -# Example 9 -try: - len(tree) -except: - logging.exception('Expected') -else: - assert False - - -# Example 10 -class SequenceNode(IndexableNode): - def __len__(self): - for count, _ in enumerate(self._traverse(), 1): - pass - return count - - -# Example 11 -tree = SequenceNode( - 10, - left=SequenceNode( - 5, - left=SequenceNode(2), - right=SequenceNode( - 6, - right=SequenceNode(7))), - right=SequenceNode( - 15, - left=SequenceNode(11)) -) - -print('Tree length is', len(tree)) - - -# Example 12 -try: - # Make sure that this doesn't work - tree.count(4) -except: - logging.exception('Expected') -else: - assert False - - -# Example 13 -try: - from collections.abc import Sequence - - class BadType(Sequence): - pass - - foo = BadType() -except: - logging.exception('Expected') -else: - assert False - - -# Example 14 -class BetterNode(SequenceNode, Sequence): - pass - -tree = BetterNode( - 10, - left=BetterNode( - 5, - left=BetterNode(2), - right=BetterNode( - 6, - right=BetterNode(7))), - right=BetterNode( - 15, - left=BetterNode(11)) -) - -print('Index of 7 is', tree.index(7)) -print('Count of 10 is', tree.count(10)) diff --git a/example_code/item_44.py b/example_code/item_44.py deleted file mode 100755 index a2062f5..0000000 --- a/example_code/item_44.py +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class OldResistor: - def __init__(self, ohms): - self._ohms = ohms - - def get_ohms(self): - return self._ohms - - def set_ohms(self, ohms): - self._ohms = ohms - - -# Example 2 -r0 = OldResistor(50e3) -print('Before:', r0.get_ohms()) -r0.set_ohms(10e3) -print('After: ', r0.get_ohms()) - - -# Example 3 -r0.set_ohms(r0.get_ohms() - 4e3) -assert r0.get_ohms() == 6e3 - - -# Example 4 -class Resistor: - def __init__(self, ohms): - self.ohms = ohms - self.voltage = 0 - self.current = 0 - -r1 = Resistor(50e3) -r1.ohms = 10e3 -print(f'{r1.ohms} ohms, ' - f'{r1.voltage} volts, ' - f'{r1.current} amps') - - -# Example 5 -r1.ohms += 5e3 - - -# Example 6 -class VoltageResistance(Resistor): - def __init__(self, ohms): - super().__init__(ohms) - self._voltage = 0 - - @property - def voltage(self): - return self._voltage - - @voltage.setter - def voltage(self, voltage): - self._voltage = voltage - self.current = self._voltage / self.ohms - - -# Example 7 -r2 = VoltageResistance(1e3) -print(f'Before: {r2.current:.2f} amps') -r2.voltage = 10 -print(f'After: {r2.current:.2f} amps') - - -# Example 8 -class BoundedResistance(Resistor): - def __init__(self, ohms): - super().__init__(ohms) - - @property - def ohms(self): - return self._ohms - - @ohms.setter - def ohms(self, ohms): - if ohms <= 0: - raise ValueError(f'ohms must be > 0; got {ohms}') - self._ohms = ohms - - -# Example 9 -try: - r3 = BoundedResistance(1e3) - r3.ohms = 0 -except: - logging.exception('Expected') -else: - assert False - - -# Example 10 -try: - BoundedResistance(-5) -except: - logging.exception('Expected') -else: - assert False - - -# Example 11 -class FixedResistance(Resistor): - def __init__(self, ohms): - super().__init__(ohms) - - @property - def ohms(self): - return self._ohms - - @ohms.setter - def ohms(self, ohms): - if hasattr(self, '_ohms'): - raise AttributeError("Ohms is immutable") - self._ohms = ohms - - -# Example 12 -try: - r4 = FixedResistance(1e3) - r4.ohms = 2e3 -except: - logging.exception('Expected') -else: - assert False - - -# Example 13 -class MysteriousResistor(Resistor): - @property - def ohms(self): - self.voltage = self._ohms * self.current - return self._ohms - - @ohms.setter - def ohms(self, ohms): - self._ohms = ohms - - -# Example 14 -r7 = MysteriousResistor(10) -r7.current = 0.01 -print(f'Before: {r7.voltage:.2f}') -r7.ohms -print(f'After: {r7.voltage:.2f}') diff --git a/example_code/item_45.py b/example_code/item_45.py deleted file mode 100755 index 6d3c6a5..0000000 --- a/example_code/item_45.py +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -from datetime import datetime, timedelta - -class Bucket: - def __init__(self, period): - self.period_delta = timedelta(seconds=period) - self.reset_time = datetime.now() - self.quota = 0 - - def __repr__(self): - return f'Bucket(quota={self.quota})' - -bucket = Bucket(60) -print(bucket) - - -# Example 2 -def fill(bucket, amount): - now = datetime.now() - if (now - bucket.reset_time) > bucket.period_delta: - bucket.quota = 0 - bucket.reset_time = now - bucket.quota += amount - - -# Example 3 -def deduct(bucket, amount): - now = datetime.now() - if (now - bucket.reset_time) > bucket.period_delta: - return False # Bucket hasn't been filled this period - if bucket.quota - amount < 0: - return False # Bucket was filled, but not enough - bucket.quota -= amount - return True # Bucket had enough, quota consumed - - -# Example 4 -bucket = Bucket(60) -fill(bucket, 100) -print(bucket) - - -# Example 5 -if deduct(bucket, 99): - print('Had 99 quota') -else: - print('Not enough for 99 quota') -print(bucket) - - -# Example 6 -if deduct(bucket, 3): - print('Had 3 quota') -else: - print('Not enough for 3 quota') -print(bucket) - - -# Example 7 -class NewBucket: - def __init__(self, period): - self.period_delta = timedelta(seconds=period) - self.reset_time = datetime.now() - self.max_quota = 0 - self.quota_consumed = 0 - - def __repr__(self): - return (f'NewBucket(max_quota={self.max_quota}, ' - f'quota_consumed={self.quota_consumed})') - - -# Example 8 - @property - def quota(self): - return self.max_quota - self.quota_consumed - - -# Example 9 - @quota.setter - def quota(self, amount): - delta = self.max_quota - amount - if amount == 0: - # Quota being reset for a new period - self.quota_consumed = 0 - self.max_quota = 0 - elif delta < 0: - # Quota being filled during the period - self.max_quota = amount + self.quota_consumed - else: - # Quota being consumed during the period - self.quota_consumed = delta - - -# Example 10 -bucket = NewBucket(60) -print('Initial', bucket) -fill(bucket, 100) -print('Filled', bucket) - -if deduct(bucket, 99): - print('Had 99 quota') -else: - print('Not enough for 99 quota') - -print('Now', bucket) - -if deduct(bucket, 3): - print('Had 3 quota') -else: - print('Not enough for 3 quota') - -print('Still', bucket) - - -# Example 11 -bucket = NewBucket(6000) -assert bucket.max_quota == 0 -assert bucket.quota_consumed == 0 -assert bucket.quota == 0 - -fill(bucket, 100) -assert bucket.max_quota == 100 -assert bucket.quota_consumed == 0 -assert bucket.quota == 100 - -assert deduct(bucket, 10) -assert bucket.max_quota == 100 -assert bucket.quota_consumed == 10 -assert bucket.quota == 90 - -assert deduct(bucket, 20) -assert bucket.max_quota == 100 -assert bucket.quota_consumed == 30 -assert bucket.quota == 70 - -fill(bucket, 50) -assert bucket.max_quota == 150 -assert bucket.quota_consumed == 30 -assert bucket.quota == 120 - -assert deduct(bucket, 40) -assert bucket.max_quota == 150 -assert bucket.quota_consumed == 70 -assert bucket.quota == 80 - -assert not deduct(bucket, 81) -assert bucket.max_quota == 150 -assert bucket.quota_consumed == 70 -assert bucket.quota == 80 - -bucket.reset_time += bucket.period_delta - timedelta(1) -assert bucket.quota == 80 -assert not deduct(bucket, 79) - -fill(bucket, 1) -assert bucket.quota == 1 diff --git a/example_code/item_46.py b/example_code/item_46.py deleted file mode 100755 index fd44042..0000000 --- a/example_code/item_46.py +++ /dev/null @@ -1,227 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class Homework: - def __init__(self): - self._grade = 0 - - @property - def grade(self): - return self._grade - - @grade.setter - def grade(self, value): - if not (0 <= value <= 100): - raise ValueError( - 'Grade must be between 0 and 100') - self._grade = value - - -# Example 2 -galileo = Homework() -galileo.grade = 95 -assert galileo.grade == 95 - - -# Example 3 -class Exam: - def __init__(self): - self._writing_grade = 0 - self._math_grade = 0 - - @staticmethod - def _check_grade(value): - if not (0 <= value <= 100): - raise ValueError( - 'Grade must be between 0 and 100') - - -# Example 4 - @property - def writing_grade(self): - return self._writing_grade - - @writing_grade.setter - def writing_grade(self, value): - self._check_grade(value) - self._writing_grade = value - - @property - def math_grade(self): - return self._math_grade - - @math_grade.setter - def math_grade(self, value): - self._check_grade(value) - self._math_grade = value - -galileo = Exam() -galileo.writing_grade = 85 -galileo.math_grade = 99 - -assert galileo.writing_grade == 85 -assert galileo.math_grade == 99 - - -# Example 5 -class Grade: - def __get__(self, instance, instance_type): - pass - - def __set__(self, instance, value): - pass - -class Exam: - # Class attributes - math_grade = Grade() - writing_grade = Grade() - science_grade = Grade() - - -# Example 6 -exam = Exam() -exam.writing_grade = 40 - - -# Example 7 -Exam.__dict__['writing_grade'].__set__(exam, 40) - - -# Example 8 -exam.writing_grade - - -# Example 9 -Exam.__dict__['writing_grade'].__get__(exam, Exam) - - -# Example 10 -class Grade: - def __init__(self): - self._value = 0 - - def __get__(self, instance, instance_type): - return self._value - - def __set__(self, instance, value): - if not (0 <= value <= 100): - raise ValueError( - 'Grade must be between 0 and 100') - self._value = value - - -# Example 11 -class Exam: - math_grade = Grade() - writing_grade = Grade() - science_grade = Grade() - -first_exam = Exam() -first_exam.writing_grade = 82 -first_exam.science_grade = 99 -print('Writing', first_exam.writing_grade) -print('Science', first_exam.science_grade) - - -# Example 12 -second_exam = Exam() -second_exam.writing_grade = 75 -print(f'Second {second_exam.writing_grade} is right') -print(f'First {first_exam.writing_grade} is wrong; ' - f'should be 82') - - -# Example 13 -class Grade: - def __init__(self): - self._values = {} - - def __get__(self, instance, instance_type): - if instance is None: - return self - return self._values.get(instance, 0) - - def __set__(self, instance, value): - if not (0 <= value <= 100): - raise ValueError( - 'Grade must be between 0 and 100') - self._values[instance] = value - - -# Example 14 -from weakref import WeakKeyDictionary - -class Grade: - def __init__(self): - self._values = WeakKeyDictionary() - - def __get__(self, instance, instance_type): - if instance is None: - return self - return self._values.get(instance, 0) - - def __set__(self, instance, value): - if not (0 <= value <= 100): - raise ValueError( - 'Grade must be between 0 and 100') - self._values[instance] = value - - -# Example 15 -class Exam: - math_grade = Grade() - writing_grade = Grade() - science_grade = Grade() - -first_exam = Exam() -first_exam.writing_grade = 82 -second_exam = Exam() -second_exam.writing_grade = 75 -print(f'First {first_exam.writing_grade} is right') -print(f'Second {second_exam.writing_grade} is right') diff --git a/example_code/item_47.py b/example_code/item_47.py deleted file mode 100755 index 7f93c5f..0000000 --- a/example_code/item_47.py +++ /dev/null @@ -1,195 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class LazyRecord: - def __init__(self): - self.exists = 5 - - def __getattr__(self, name): - value = f'Value for {name}' - setattr(self, name, value) - return value - - -# Example 2 -data = LazyRecord() -print('Before:', data.__dict__) -print('foo: ', data.foo) -print('After: ', data.__dict__) - - -# Example 3 -class LoggingLazyRecord(LazyRecord): - def __getattr__(self, name): - print(f'* Called __getattr__({name!r}), ' - f'populating instance dictionary') - result = super().__getattr__(name) - print(f'* Returning {result!r}') - return result - -data = LoggingLazyRecord() -print('exists: ', data.exists) -print('First foo: ', data.foo) -print('Second foo: ', data.foo) - - -# Example 4 -class ValidatingRecord: - def __init__(self): - self.exists = 5 - - def __getattribute__(self, name): - print(f'* Called __getattribute__({name!r})') - try: - value = super().__getattribute__(name) - print(f'* Found {name!r}, returning {value!r}') - return value - except AttributeError: - value = f'Value for {name}' - print(f'* Setting {name!r} to {value!r}') - setattr(self, name, value) - return value - -data = ValidatingRecord() -print('exists: ', data.exists) -print('First foo: ', data.foo) -print('Second foo: ', data.foo) - - -# Example 5 -try: - class MissingPropertyRecord: - def __getattr__(self, name): - if name == 'bad_name': - raise AttributeError(f'{name} is missing') - value = f'Value for {name}' - setattr(self, name, value) - return value - - data = MissingPropertyRecord() - assert data.foo == 'Value for foo' # Test this works - data.bad_name -except: - logging.exception('Expected') -else: - assert False - - -# Example 6 -data = LoggingLazyRecord() # Implements __getattr__ -print('Before: ', data.__dict__) -print('Has first foo: ', hasattr(data, 'foo')) -print('After: ', data.__dict__) -print('Has second foo: ', hasattr(data, 'foo')) - - -# Example 7 -data = ValidatingRecord() # Implements __getattribute__ -print('Has first foo: ', hasattr(data, 'foo')) -print('Has second foo: ', hasattr(data, 'foo')) - - -# Example 8 -class SavingRecord: - def __setattr__(self, name, value): - # Save some data for the record - pass - super().__setattr__(name, value) - - -# Example 9 -class LoggingSavingRecord(SavingRecord): - def __setattr__(self, name, value): - print(f'* Called __setattr__({name!r}, {value!r})') - super().__setattr__(name, value) - -data = LoggingSavingRecord() -print('Before: ', data.__dict__) -data.foo = 5 -print('After: ', data.__dict__) -data.foo = 7 -print('Finally:', data.__dict__) - - -# Example 10 -class BrokenDictionaryRecord: - def __init__(self, data): - self._data = {} - - def __getattribute__(self, name): - print(f'* Called __getattribute__({name!r})') - return self._data[name] - - -# Example 11 -try: - data = BrokenDictionaryRecord({'foo': 3}) - data.foo -except: - logging.exception('Expected') -else: - assert False - - -# Example 12 -class DictionaryRecord: - def __init__(self, data): - self._data = data - - def __getattribute__(self, name): - # Prevent weird interactions with isinstance() used - # by example code harness. - if name == '__class__': - return DictionaryRecord - print(f'* Called __getattribute__({name!r})') - data_dict = super().__getattribute__('_data') - return data_dict[name] - -data = DictionaryRecord({'foo': 3}) -print('foo: ', data.foo) diff --git a/example_code/item_48.py b/example_code/item_48.py deleted file mode 100755 index 5533175..0000000 --- a/example_code/item_48.py +++ /dev/null @@ -1,303 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class Meta(type): - def __new__(meta, name, bases, class_dict): - global print - orig_print = print - print(f'* Running {meta}.__new__ for {name}') - print('Bases:', bases) - print = pprint - print(class_dict) - print = orig_print - return type.__new__(meta, name, bases, class_dict) - -class MyClass(metaclass=Meta): - stuff = 123 - - def foo(self): - pass - -class MySubclass(MyClass): - other = 567 - - def bar(self): - pass - - -# Example 2 -class ValidatePolygon(type): - def __new__(meta, name, bases, class_dict): - # Only validate subclasses of the Polygon class - if bases: - if class_dict['sides'] < 3: - raise ValueError('Polygons need 3+ sides') - return type.__new__(meta, name, bases, class_dict) - -class Polygon(metaclass=ValidatePolygon): - sides = None # Must be specified by subclasses - - @classmethod - def interior_angles(cls): - return (cls.sides - 2) * 180 - -class Triangle(Polygon): - sides = 3 - -class Rectangle(Polygon): - sides = 4 - -class Nonagon(Polygon): - sides = 9 - -assert Triangle.interior_angles() == 180 -assert Rectangle.interior_angles() == 360 -assert Nonagon.interior_angles() == 1260 - - -# Example 3 -try: - print('Before class') - - class Line(Polygon): - print('Before sides') - sides = 2 - print('After sides') - - print('After class') -except: - logging.exception('Expected') -else: - assert False - - -# Example 4 -class BetterPolygon: - sides = None # Must be specified by subclasses - - def __init_subclass__(cls): - super().__init_subclass__() - if cls.sides < 3: - raise ValueError('Polygons need 3+ sides') - - @classmethod - def interior_angles(cls): - return (cls.sides - 2) * 180 - -class Hexagon(BetterPolygon): - sides = 6 - -assert Hexagon.interior_angles() == 720 - - -# Example 5 -try: - print('Before class') - - class Point(BetterPolygon): - sides = 1 - - print('After class') -except: - logging.exception('Expected') -else: - assert False - - -# Example 6 -class ValidateFilled(type): - def __new__(meta, name, bases, class_dict): - # Only validate subclasses of the Filled class - if bases: - if class_dict['color'] not in ('red', 'green'): - raise ValueError('Fill color must be supported') - return type.__new__(meta, name, bases, class_dict) - -class Filled(metaclass=ValidateFilled): - color = None # Must be specified by subclasses - - -# Example 7 -try: - class RedPentagon(Filled, Polygon): - color = 'blue' - sides = 5 -except: - logging.exception('Expected') -else: - assert False - - -# Example 8 -class ValidatePolygon(type): - def __new__(meta, name, bases, class_dict): - # Only validate non-root classes - if not class_dict.get('is_root'): - if class_dict['sides'] < 3: - raise ValueError('Polygons need 3+ sides') - return type.__new__(meta, name, bases, class_dict) - -class Polygon(metaclass=ValidatePolygon): - is_root = True - sides = None # Must be specified by subclasses - -class ValidateFilledPolygon(ValidatePolygon): - def __new__(meta, name, bases, class_dict): - # Only validate non-root classes - if not class_dict.get('is_root'): - if class_dict['color'] not in ('red', 'green'): - raise ValueError('Fill color must be supported') - return super().__new__(meta, name, bases, class_dict) - -class FilledPolygon(Polygon, metaclass=ValidateFilledPolygon): - is_root = True - color = None # Must be specified by subclasses - - -# Example 9 -class GreenPentagon(FilledPolygon): - color = 'green' - sides = 5 - -greenie = GreenPentagon() -assert isinstance(greenie, Polygon) - - -# Example 10 -try: - class OrangePentagon(FilledPolygon): - color = 'orange' - sides = 5 -except: - logging.exception('Expected') -else: - assert False - - -# Example 11 -try: - class RedLine(FilledPolygon): - color = 'red' - sides = 2 -except: - logging.exception('Expected') -else: - assert False - - -# Example 12 -class Filled: - color = None # Must be specified by subclasses - - def __init_subclass__(cls): - super().__init_subclass__() - if cls.color not in ('red', 'green', 'blue'): - raise ValueError('Fills need a valid color') - - -# Example 13 -class RedTriangle(Filled, BetterPolygon): - color = 'red' - sides = 3 - -ruddy = RedTriangle() -assert isinstance(ruddy, Filled) -assert isinstance(ruddy, BetterPolygon) - - -# Example 14 -try: - print('Before class') - - class BlueLine(Filled, BetterPolygon): - color = 'blue' - sides = 2 - - print('After class') -except: - logging.exception('Expected') -else: - assert False - - -# Example 15 -try: - print('Before class') - - class BeigeSquare(Filled, BetterPolygon): - color = 'beige' - sides = 4 - - print('After class') -except: - logging.exception('Expected') -else: - assert False - - -# Example 16 -class Top: - def __init_subclass__(cls): - super().__init_subclass__() - print(f'Top for {cls}') - -class Left(Top): - def __init_subclass__(cls): - super().__init_subclass__() - print(f'Left for {cls}') - -class Right(Top): - def __init_subclass__(cls): - super().__init_subclass__() - print(f'Right for {cls}') - -class Bottom(Left, Right): - def __init_subclass__(cls): - super().__init_subclass__() - print(f'Bottom for {cls}') diff --git a/example_code/item_49.py b/example_code/item_49.py deleted file mode 100755 index 7dec89a..0000000 --- a/example_code/item_49.py +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -import json - -class Serializable: - def __init__(self, *args): - self.args = args - - def serialize(self): - return json.dumps({'args': self.args}) - - -# Example 2 -class Point2D(Serializable): - def __init__(self, x, y): - super().__init__(x, y) - self.x = x - self.y = y - - def __repr__(self): - return f'Point2D({self.x}, {self.y})' - -point = Point2D(5, 3) -print('Object: ', point) -print('Serialized:', point.serialize()) - - -# Example 3 -class Deserializable(Serializable): - @classmethod - def deserialize(cls, json_data): - params = json.loads(json_data) - return cls(*params['args']) - - -# Example 4 -class BetterPoint2D(Deserializable): - def __init__(self, x, y): - super().__init__(x, y) - self.x = x - self.y = y - - def __repr__(self): - return f'Point2D({self.x}, {self.y})' - -before = BetterPoint2D(5, 3) -print('Before: ', before) -data = before.serialize() -print('Serialized:', data) -after = BetterPoint2D.deserialize(data) -print('After: ', after) - - -# Example 5 -class BetterSerializable: - def __init__(self, *args): - self.args = args - - def serialize(self): - return json.dumps({ - 'class': self.__class__.__name__, - 'args': self.args, - }) - - def __repr__(self): - name = self.__class__.__name__ - args_str = ', '.join(str(x) for x in self.args) - return f'{name}({args_str})' - - -# Example 6 -registry = {} - -def register_class(target_class): - registry[target_class.__name__] = target_class - -def deserialize(data): - params = json.loads(data) - name = params['class'] - target_class = registry[name] - return target_class(*params['args']) - - -# Example 7 -class EvenBetterPoint2D(BetterSerializable): - def __init__(self, x, y): - super().__init__(x, y) - self.x = x - self.y = y - -register_class(EvenBetterPoint2D) - - -# Example 8 -before = EvenBetterPoint2D(5, 3) -print('Before: ', before) -data = before.serialize() -print('Serialized:', data) -after = deserialize(data) -print('After: ', after) - - -# Example 9 -class Point3D(BetterSerializable): - def __init__(self, x, y, z): - super().__init__(x, y, z) - self.x = x - self.y = y - self.z = z - -# Forgot to call register_class! Whoops! - - -# Example 10 -try: - point = Point3D(5, 9, -4) - data = point.serialize() - deserialize(data) -except: - logging.exception('Expected') -else: - assert False - - -# Example 11 -class Meta(type): - def __new__(meta, name, bases, class_dict): - cls = type.__new__(meta, name, bases, class_dict) - register_class(cls) - return cls - -class RegisteredSerializable(BetterSerializable, - metaclass=Meta): - pass - - -# Example 12 -class Vector3D(RegisteredSerializable): - def __init__(self, x, y, z): - super().__init__(x, y, z) - self.x, self.y, self.z = x, y, z - -before = Vector3D(10, -7, 3) -print('Before: ', before) -data = before.serialize() -print('Serialized:', data) -print('After: ', deserialize(data)) - - -# Example 13 -class BetterRegisteredSerializable(BetterSerializable): - def __init_subclass__(cls): - super().__init_subclass__() - register_class(cls) - -class Vector1D(BetterRegisteredSerializable): - def __init__(self, magnitude): - super().__init__(magnitude) - self.magnitude = magnitude - -before = Vector1D(6) -print('Before: ', before) -data = before.serialize() -print('Serialized:', data) -print('After: ', deserialize(data)) diff --git a/example_code/item_50.py b/example_code/item_50.py deleted file mode 100755 index 2a26765..0000000 --- a/example_code/item_50.py +++ /dev/null @@ -1,182 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class Field: - def __init__(self, name): - self.name = name - self.internal_name = '_' + self.name - - def __get__(self, instance, instance_type): - if instance is None: - return self - return getattr(instance, self.internal_name, '') - - def __set__(self, instance, value): - setattr(instance, self.internal_name, value) - - -# Example 2 -class Customer: - # Class attributes - first_name = Field('first_name') - last_name = Field('last_name') - prefix = Field('prefix') - suffix = Field('suffix') - - -# Example 3 -cust = Customer() -print(f'Before: {cust.first_name!r} {cust.__dict__}') -cust.first_name = 'Euclid' -print(f'After: {cust.first_name!r} {cust.__dict__}') - - -# Example 4 -class Customer: - # Left side is redundant with right side - first_name = Field('first_name') - last_name = Field('last_name') - prefix = Field('prefix') - suffix = Field('suffix') - - -# Example 5 -class Meta(type): - def __new__(meta, name, bases, class_dict): - for key, value in class_dict.items(): - if isinstance(value, Field): - value.name = key - value.internal_name = '_' + key - cls = type.__new__(meta, name, bases, class_dict) - return cls - - -# Example 6 -class DatabaseRow(metaclass=Meta): - pass - - -# Example 7 -class Field: - def __init__(self): - # These will be assigned by the metaclass. - self.name = None - self.internal_name = None - - def __get__(self, instance, instance_type): - if instance is None: - return self - return getattr(instance, self.internal_name, '') - - def __set__(self, instance, value): - setattr(instance, self.internal_name, value) - - -# Example 8 -class BetterCustomer(DatabaseRow): - first_name = Field() - last_name = Field() - prefix = Field() - suffix = Field() - - -# Example 9 -cust = BetterCustomer() -print(f'Before: {cust.first_name!r} {cust.__dict__}') -cust.first_name = 'Euler' -print(f'After: {cust.first_name!r} {cust.__dict__}') - - -# Example 10 -try: - class BrokenCustomer: - first_name = Field() - last_name = Field() - prefix = Field() - suffix = Field() - - cust = BrokenCustomer() - cust.first_name = 'Mersenne' -except: - logging.exception('Expected') -else: - assert False - - -# Example 11 -class Field: - def __init__(self): - self.name = None - self.internal_name = None - - def __set_name__(self, owner, name): - # Called on class creation for each descriptor - self.name = name - self.internal_name = '_' + name - - def __get__(self, instance, instance_type): - if instance is None: - return self - return getattr(instance, self.internal_name, '') - - def __set__(self, instance, value): - setattr(instance, self.internal_name, value) - - -# Example 12 -class FixedCustomer: - first_name = Field() - last_name = Field() - prefix = Field() - suffix = Field() - -cust = FixedCustomer() -print(f'Before: {cust.first_name!r} {cust.__dict__}') -cust.first_name = 'Mersenne' -print(f'After: {cust.first_name!r} {cust.__dict__}') diff --git a/example_code/item_51.py b/example_code/item_51.py deleted file mode 100755 index 0beacd7..0000000 --- a/example_code/item_51.py +++ /dev/null @@ -1,243 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -from functools import wraps - -def trace_func(func): - if hasattr(func, 'tracing'): # Only decorate once - return func - - @wraps(func) - def wrapper(*args, **kwargs): - result = None - try: - result = func(*args, **kwargs) - return result - except Exception as e: - result = e - raise - finally: - print(f'{func.__name__}({args!r}, {kwargs!r}) -> ' - f'{result!r}') - - wrapper.tracing = True - return wrapper - - -# Example 2 -class TraceDict(dict): - @trace_func - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - @trace_func - def __setitem__(self, *args, **kwargs): - return super().__setitem__(*args, **kwargs) - - @trace_func - def __getitem__(self, *args, **kwargs): - return super().__getitem__(*args, **kwargs) - - -# Example 3 -trace_dict = TraceDict([('hi', 1)]) -trace_dict['there'] = 2 -trace_dict['hi'] -try: - trace_dict['does not exist'] -except KeyError: - pass # Expected -else: - assert False - - -# Example 4 -import types - -trace_types = ( - types.MethodType, - types.FunctionType, - types.BuiltinFunctionType, - types.BuiltinMethodType, - types.MethodDescriptorType, - types.ClassMethodDescriptorType) - -class TraceMeta(type): - def __new__(meta, name, bases, class_dict): - klass = super().__new__(meta, name, bases, class_dict) - - for key in dir(klass): - value = getattr(klass, key) - if isinstance(value, trace_types): - wrapped = trace_func(value) - setattr(klass, key, wrapped) - - return klass - - -# Example 5 -class TraceDict(dict, metaclass=TraceMeta): - pass - -trace_dict = TraceDict([('hi', 1)]) -trace_dict['there'] = 2 -trace_dict['hi'] -try: - trace_dict['does not exist'] -except KeyError: - pass # Expected -else: - assert False - - -# Example 6 -try: - class OtherMeta(type): - pass - - class SimpleDict(dict, metaclass=OtherMeta): - pass - - class TraceDict(SimpleDict, metaclass=TraceMeta): - pass -except: - logging.exception('Expected') -else: - assert False - - -# Example 7 -class TraceMeta(type): - def __new__(meta, name, bases, class_dict): - klass = type.__new__(meta, name, bases, class_dict) - - for key in dir(klass): - value = getattr(klass, key) - if isinstance(value, trace_types): - wrapped = trace_func(value) - setattr(klass, key, wrapped) - - return klass - -class OtherMeta(TraceMeta): - pass - -class SimpleDict(dict, metaclass=OtherMeta): - pass - -class TraceDict(SimpleDict, metaclass=TraceMeta): - pass - -trace_dict = TraceDict([('hi', 1)]) -trace_dict['there'] = 2 -trace_dict['hi'] -try: - trace_dict['does not exist'] -except KeyError: - pass # Expected -else: - assert False - - -# Example 8 -def my_class_decorator(klass): - klass.extra_param = 'hello' - return klass - -@my_class_decorator -class MyClass: - pass - -print(MyClass) -print(MyClass.extra_param) - - -# Example 9 -def trace(klass): - for key in dir(klass): - value = getattr(klass, key) - if isinstance(value, trace_types): - wrapped = trace_func(value) - setattr(klass, key, wrapped) - return klass - - -# Example 10 -@trace -class TraceDict(dict): - pass - -trace_dict = TraceDict([('hi', 1)]) -trace_dict['there'] = 2 -trace_dict['hi'] -try: - trace_dict['does not exist'] -except KeyError: - pass # Expected -else: - assert False - - -# Example 11 -class OtherMeta(type): - pass - -@trace -class TraceDict(dict, metaclass=OtherMeta): - pass - -trace_dict = TraceDict([('hi', 1)]) -trace_dict['there'] = 2 -trace_dict['hi'] -try: - trace_dict['does not exist'] -except KeyError: - pass # Expected -else: - assert False diff --git a/example_code/item_52.py b/example_code/item_52.py deleted file mode 100755 index d112055..0000000 --- a/example_code/item_52.py +++ /dev/null @@ -1,182 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -import subprocess -# Enable these lines to make this example work on Windows -# import os -# os.environ['COMSPEC'] = 'powershell' - -result = subprocess.run( - ['echo', 'Hello from the child!'], - capture_output=True, - # Enable this line to make this example work on Windows - # shell=True, - encoding='utf-8') - -result.check_returncode() # No exception means it exited cleanly -print(result.stdout) - - -# Example 2 -# Use this line instead to make this example work on Windows -# proc = subprocess.Popen(['sleep', '1'], shell=True) -proc = subprocess.Popen(['sleep', '1']) -while proc.poll() is None: - print('Working...') - # Some time-consuming work here - import time - time.sleep(0.3) - -print('Exit status', proc.poll()) - - -# Example 3 -import time - -start = time.time() -sleep_procs = [] -for _ in range(10): - # Use this line instead to make this example work on Windows - # proc = subprocess.Popen(['sleep', '1'], shell=True) - proc = subprocess.Popen(['sleep', '1']) - sleep_procs.append(proc) - - -# Example 4 -for proc in sleep_procs: - proc.communicate() - -end = time.time() -delta = end - start -print(f'Finished in {delta:.3} seconds') - - -# Example 5 -import os -# On Windows, after installing OpenSSL, you may need to -# alias it in your PowerShell path with a command like: -# $env:path = $env:path + ";C:\Program Files\OpenSSL-Win64\bin" - -def run_encrypt(data): - env = os.environ.copy() - env['password'] = 'zf7ShyBhZOraQDdE/FiZpm/m/8f9X+M1' - proc = subprocess.Popen( - ['openssl', 'enc', '-des3', '-pass', 'env:password'], - env=env, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE) - proc.stdin.write(data) - proc.stdin.flush() # Ensure that the child gets input - return proc - - -# Example 6 -procs = [] -for _ in range(3): - data = os.urandom(10) - proc = run_encrypt(data) - procs.append(proc) - - -# Example 7 -for proc in procs: - out, _ = proc.communicate() - print(out[-10:]) - - -# Example 8 -def run_hash(input_stdin): - return subprocess.Popen( - ['openssl', 'dgst', '-whirlpool', '-binary'], - stdin=input_stdin, - stdout=subprocess.PIPE) - - -# Example 9 -encrypt_procs = [] -hash_procs = [] -for _ in range(3): - data = os.urandom(100) - - encrypt_proc = run_encrypt(data) - encrypt_procs.append(encrypt_proc) - - hash_proc = run_hash(encrypt_proc.stdout) - hash_procs.append(hash_proc) - - # Ensure that the child consumes the input stream and - # the communicate() method doesn't inadvertently steal - # input from the child. Also lets SIGPIPE propagate to - # the upstream process if the downstream process dies. - encrypt_proc.stdout.close() - encrypt_proc.stdout = None - - -# Example 10 -for proc in encrypt_procs: - proc.communicate() - assert proc.returncode == 0 - -for proc in hash_procs: - out, _ = proc.communicate() - print(out[-10:]) - assert proc.returncode == 0 - - -# Example 11 -# Use this line instead to make this example work on Windows -# proc = subprocess.Popen(['sleep', '10'], shell=True) -proc = subprocess.Popen(['sleep', '10']) -try: - proc.communicate(timeout=0.1) -except subprocess.TimeoutExpired: - proc.terminate() - proc.wait() - -print('Exit status', proc.poll()) diff --git a/example_code/item_53.py b/example_code/item_53.py deleted file mode 100755 index fdab48f..0000000 --- a/example_code/item_53.py +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -def factorize(number): - for i in range(1, number + 1): - if number % i == 0: - yield i - - -# Example 2 -import time - -numbers = [2139079, 1214759, 1516637, 1852285] -start = time.time() - -for number in numbers: - list(factorize(number)) - -end = time.time() -delta = end - start -print(f'Took {delta:.3f} seconds') - - -# Example 3 -from threading import Thread - -class FactorizeThread(Thread): - def __init__(self, number): - super().__init__() - self.number = number - - def run(self): - self.factors = list(factorize(self.number)) - - -# Example 4 -start = time.time() - -threads = [] -for number in numbers: - thread = FactorizeThread(number) - thread.start() - threads.append(thread) - - -# Example 5 -for thread in threads: - thread.join() - -end = time.time() -delta = end - start -print(f'Took {delta:.3f} seconds') - - -# Example 6 -import select -import socket - -def slow_systemcall(): - select.select([socket.socket()], [], [], 0.1) - - -# Example 7 -start = time.time() - -for _ in range(5): - slow_systemcall() - -end = time.time() -delta = end - start -print(f'Took {delta:.3f} seconds') - - -# Example 8 -start = time.time() - -threads = [] -for _ in range(5): - thread = Thread(target=slow_systemcall) - thread.start() - threads.append(thread) - - -# Example 9 -def compute_helicopter_location(index): - pass - -for i in range(5): - compute_helicopter_location(i) - -for thread in threads: - thread.join() - -end = time.time() -delta = end - start -print(f'Took {delta:.3f} seconds') diff --git a/example_code/item_54.py b/example_code/item_54.py deleted file mode 100755 index 60636f4..0000000 --- a/example_code/item_54.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class Counter: - def __init__(self): - self.count = 0 - - def increment(self, offset): - self.count += offset - - -# Example 2 -def worker(sensor_index, how_many, counter): - # I have a barrier in here so the workers synchronize - # when they start counting, otherwise it's hard to get a race - # because the overhead of starting a thread is high. - BARRIER.wait() - for _ in range(how_many): - # Read from the sensor - # Nothing actually happens here, but this is where - # the blocking I/O would go. - counter.increment(1) - - -# Example 3 -from threading import Barrier -BARRIER = Barrier(5) -from threading import Thread - -how_many = 10**5 -counter = Counter() - -threads = [] -for i in range(5): - thread = Thread(target=worker, - args=(i, how_many, counter)) - threads.append(thread) - thread.start() - -for thread in threads: - thread.join() - -expected = how_many * 5 -found = counter.count -print(f'Counter should be {expected}, got {found}') - - -# Example 4 -counter.count += 1 - - -# Example 5 -value = getattr(counter, 'count') -result = value + 1 -setattr(counter, 'count', result) - - -# Example 6 -# Running in Thread A -value_a = getattr(counter, 'count') -# Context switch to Thread B -value_b = getattr(counter, 'count') -result_b = value_b + 1 -setattr(counter, 'count', result_b) -# Context switch back to Thread A -result_a = value_a + 1 -setattr(counter, 'count', result_a) - - -# Example 7 -from threading import Lock - -class LockingCounter: - def __init__(self): - self.lock = Lock() - self.count = 0 - - def increment(self, offset): - with self.lock: - self.count += offset - - -# Example 8 -BARRIER = Barrier(5) -counter = LockingCounter() - -for i in range(5): - thread = Thread(target=worker, - args=(i, how_many, counter)) - threads.append(thread) - thread.start() - -for thread in threads: - thread.join() - -expected = how_many * 5 -found = counter.count -print(f'Counter should be {expected}, got {found}') diff --git a/example_code/item_55.py b/example_code/item_55.py deleted file mode 100755 index 86696b2..0000000 --- a/example_code/item_55.py +++ /dev/null @@ -1,325 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -def download(item): - return item - -def resize(item): - return item - -def upload(item): - return item - - -# Example 2 -from collections import deque -from threading import Lock - -class MyQueue: - def __init__(self): - self.items = deque() - self.lock = Lock() - - -# Example 3 - def put(self, item): - with self.lock: - self.items.append(item) - - -# Example 4 - def get(self): - with self.lock: - return self.items.popleft() - - -# Example 5 -from threading import Thread -import time - -class Worker(Thread): - def __init__(self, func, in_queue, out_queue): - super().__init__() - self.func = func - self.in_queue = in_queue - self.out_queue = out_queue - self.polled_count = 0 - self.work_done = 0 - - -# Example 6 - def run(self): - while True: - self.polled_count += 1 - try: - item = self.in_queue.get() - except IndexError: - time.sleep(0.01) # No work to do - except AttributeError: - # The magic exit signal - return - else: - result = self.func(item) - self.out_queue.put(result) - self.work_done += 1 - - -# Example 7 -download_queue = MyQueue() -resize_queue = MyQueue() -upload_queue = MyQueue() -done_queue = MyQueue() -threads = [ - Worker(download, download_queue, resize_queue), - Worker(resize, resize_queue, upload_queue), - Worker(upload, upload_queue, done_queue), -] - - -# Example 8 -for thread in threads: - thread.start() - -for _ in range(1000): - download_queue.put(object()) - - -# Example 9 -while len(done_queue.items) < 1000: - # Do something useful while waiting - time.sleep(0.1) -# Stop all the threads by causing an exception in their -# run methods. -for thread in threads: - thread.in_queue = None - thread.join() - - -# Example 10 -processed = len(done_queue.items) -polled = sum(t.polled_count for t in threads) -print(f'Processed {processed} items after ' - f'polling {polled} times') - - -# Example 11 -from queue import Queue - -my_queue = Queue() - -def consumer(): - print('Consumer waiting') - my_queue.get() # Runs after put() below - print('Consumer done') - -thread = Thread(target=consumer) -thread.start() - - -# Example 12 -print('Producer putting') -my_queue.put(object()) # Runs before get() above -print('Producer done') -thread.join() - - -# Example 13 -my_queue = Queue(1) # Buffer size of 1 - -def consumer(): - time.sleep(0.1) # Wait - my_queue.get() # Runs second - print('Consumer got 1') - my_queue.get() # Runs fourth - print('Consumer got 2') - print('Consumer done') - -thread = Thread(target=consumer) -thread.start() - - -# Example 14 -my_queue.put(object()) # Runs first -print('Producer put 1') -my_queue.put(object()) # Runs third -print('Producer put 2') -print('Producer done') -thread.join() - - -# Example 15 -in_queue = Queue() - -def consumer(): - print('Consumer waiting') - work = in_queue.get() # Done second - print('Consumer working') - # Doing work - print('Consumer done') - in_queue.task_done() # Done third - -thread = Thread(target=consumer) -thread.start() - - -# Example 16 -print('Producer putting') -in_queue.put(object()) # Done first -print('Producer waiting') -in_queue.join() # Done fourth -print('Producer done') -thread.join() - - -# Example 17 -class ClosableQueue(Queue): - SENTINEL = object() - - def close(self): - self.put(self.SENTINEL) - - -# Example 18 - def __iter__(self): - while True: - item = self.get() - try: - if item is self.SENTINEL: - return # Cause the thread to exit - yield item - finally: - self.task_done() - - -# Example 19 -class StoppableWorker(Thread): - def __init__(self, func, in_queue, out_queue): - super().__init__() - self.func = func - self.in_queue = in_queue - self.out_queue = out_queue - - def run(self): - for item in self.in_queue: - result = self.func(item) - self.out_queue.put(result) - - -# Example 20 -download_queue = ClosableQueue() -resize_queue = ClosableQueue() -upload_queue = ClosableQueue() -done_queue = ClosableQueue() -threads = [ - StoppableWorker(download, download_queue, resize_queue), - StoppableWorker(resize, resize_queue, upload_queue), - StoppableWorker(upload, upload_queue, done_queue), -] - - -# Example 21 -for thread in threads: - thread.start() - -for _ in range(1000): - download_queue.put(object()) - -download_queue.close() - - -# Example 22 -download_queue.join() -resize_queue.close() -resize_queue.join() -upload_queue.close() -upload_queue.join() -print(done_queue.qsize(), 'items finished') - -for thread in threads: - thread.join() - - -# Example 23 -def start_threads(count, *args): - threads = [StoppableWorker(*args) for _ in range(count)] - for thread in threads: - thread.start() - return threads - -def stop_threads(closable_queue, threads): - for _ in threads: - closable_queue.close() - - closable_queue.join() - - for thread in threads: - thread.join() - - -# Example 24 -download_queue = ClosableQueue() -resize_queue = ClosableQueue() -upload_queue = ClosableQueue() -done_queue = ClosableQueue() - -download_threads = start_threads( - 3, download, download_queue, resize_queue) -resize_threads = start_threads( - 4, resize, resize_queue, upload_queue) -upload_threads = start_threads( - 5, upload, upload_queue, done_queue) - -for _ in range(1000): - download_queue.put(object()) - -stop_threads(download_queue, download_threads) -stop_threads(resize_queue, resize_threads) -stop_threads(upload_queue, upload_threads) - -print(done_queue.qsize(), 'items finished') diff --git a/example_code/item_56.py b/example_code/item_56.py deleted file mode 100755 index b01e83b..0000000 --- a/example_code/item_56.py +++ /dev/null @@ -1,228 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -ALIVE = '*' -EMPTY = '-' - - -# Example 2 -class Grid: - def __init__(self, height, width): - self.height = height - self.width = width - self.rows = [] - for _ in range(self.height): - self.rows.append([EMPTY] * self.width) - - def get(self, y, x): - return self.rows[y % self.height][x % self.width] - - def set(self, y, x, state): - self.rows[y % self.height][x % self.width] = state - - def __str__(self): - output = '' - for row in self.rows: - for cell in row: - output += cell - output += '\n' - return output - - -# Example 3 -grid = Grid(5, 9) -grid.set(0, 3, ALIVE) -grid.set(1, 4, ALIVE) -grid.set(2, 2, ALIVE) -grid.set(2, 3, ALIVE) -grid.set(2, 4, ALIVE) -print(grid) - - -# Example 4 -def count_neighbors(y, x, get): - n_ = get(y - 1, x + 0) # North - ne = get(y - 1, x + 1) # Northeast - e_ = get(y + 0, x + 1) # East - se = get(y + 1, x + 1) # Southeast - s_ = get(y + 1, x + 0) # South - sw = get(y + 1, x - 1) # Southwest - w_ = get(y + 0, x - 1) # West - nw = get(y - 1, x - 1) # Northwest - neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] - count = 0 - for state in neighbor_states: - if state == ALIVE: - count += 1 - return count - -alive = {(9, 5), (9, 6)} -seen = set() - -def fake_get(y, x): - position = (y, x) - seen.add(position) - return ALIVE if position in alive else EMPTY - -count = count_neighbors(10, 5, fake_get) -assert count == 2 - -expected_seen = { - (9, 5), (9, 6), (10, 6), (11, 6), - (11, 5), (11, 4), (10, 4), (9, 4) -} -assert seen == expected_seen - - -# Example 5 -def game_logic(state, neighbors): - if state == ALIVE: - if neighbors < 2: - return EMPTY # Die: Too few - elif neighbors > 3: - return EMPTY # Die: Too many - else: - if neighbors == 3: - return ALIVE # Regenerate - return state - -assert game_logic(ALIVE, 0) == EMPTY -assert game_logic(ALIVE, 1) == EMPTY -assert game_logic(ALIVE, 2) == ALIVE -assert game_logic(ALIVE, 3) == ALIVE -assert game_logic(ALIVE, 4) == EMPTY -assert game_logic(EMPTY, 0) == EMPTY -assert game_logic(EMPTY, 1) == EMPTY -assert game_logic(EMPTY, 2) == EMPTY -assert game_logic(EMPTY, 3) == ALIVE -assert game_logic(EMPTY, 4) == EMPTY - - -# Example 6 -def step_cell(y, x, get, set): - state = get(y, x) - neighbors = count_neighbors(y, x, get) - next_state = game_logic(state, neighbors) - set(y, x, next_state) - -alive = {(10, 5), (9, 5), (9, 6)} -new_state = None - -def fake_get(y, x): - return ALIVE if (y, x) in alive else EMPTY - -def fake_set(y, x, state): - global new_state - new_state = state - -# Stay alive -step_cell(10, 5, fake_get, fake_set) -assert new_state == ALIVE - -# Stay dead -alive.remove((10, 5)) -step_cell(10, 5, fake_get, fake_set) -assert new_state == EMPTY - -# Regenerate -alive.add((10, 6)) -step_cell(10, 5, fake_get, fake_set) -assert new_state == ALIVE - - -# Example 7 -def simulate(grid): - next_grid = Grid(grid.height, grid.width) - for y in range(grid.height): - for x in range(grid.width): - step_cell(y, x, grid.get, next_grid.set) - return next_grid - - -# Example 8 -class ColumnPrinter: - def __init__(self): - self.columns = [] - - def append(self, data): - self.columns.append(data) - - def __str__(self): - row_count = 1 - for data in self.columns: - row_count = max( - row_count, len(data.splitlines()) + 1) - - rows = [''] * row_count - for j in range(row_count): - for i, data in enumerate(self.columns): - line = data.splitlines()[max(0, j - 1)] - if j == 0: - padding = ' ' * (len(line) // 2) - rows[j] += padding + str(i) + padding - else: - rows[j] += line - - if (i + 1) < len(self.columns): - rows[j] += ' | ' - - return '\n'.join(rows) - -columns = ColumnPrinter() -for i in range(5): - columns.append(str(grid)) - grid = simulate(grid) - -print(columns) - - -# Example 9 -def game_logic(state, neighbors): - # Do some blocking input/output in here: - data = my_socket.recv(100) diff --git a/example_code/item_57.py b/example_code/item_57.py deleted file mode 100755 index b236a72..0000000 --- a/example_code/item_57.py +++ /dev/null @@ -1,211 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -from threading import Lock - -ALIVE = '*' -EMPTY = '-' - -class Grid: - def __init__(self, height, width): - self.height = height - self.width = width - self.rows = [] - for _ in range(self.height): - self.rows.append([EMPTY] * self.width) - - def get(self, y, x): - return self.rows[y % self.height][x % self.width] - - def set(self, y, x, state): - self.rows[y % self.height][x % self.width] = state - - def __str__(self): - output = '' - for row in self.rows: - for cell in row: - output += cell - output += '\n' - return output - -class LockingGrid(Grid): - def __init__(self, height, width): - super().__init__(height, width) - self.lock = Lock() - - def __str__(self): - with self.lock: - return super().__str__() - - def get(self, y, x): - with self.lock: - return super().get(y, x) - - def set(self, y, x, state): - with self.lock: - return super().set(y, x, state) - - -# Example 2 -from threading import Thread - -def count_neighbors(y, x, get): - n_ = get(y - 1, x + 0) # North - ne = get(y - 1, x + 1) # Northeast - e_ = get(y + 0, x + 1) # East - se = get(y + 1, x + 1) # Southeast - s_ = get(y + 1, x + 0) # South - sw = get(y + 1, x - 1) # Southwest - w_ = get(y + 0, x - 1) # West - nw = get(y - 1, x - 1) # Northwest - neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] - count = 0 - for state in neighbor_states: - if state == ALIVE: - count += 1 - return count - -def game_logic(state, neighbors): - # Do some blocking input/output in here: - data = my_socket.recv(100) - -def game_logic(state, neighbors): - if state == ALIVE: - if neighbors < 2: - return EMPTY # Die: Too few - elif neighbors > 3: - return EMPTY # Die: Too many - else: - if neighbors == 3: - return ALIVE # Regenerate - return state - -def step_cell(y, x, get, set): - state = get(y, x) - neighbors = count_neighbors(y, x, get) - next_state = game_logic(state, neighbors) - set(y, x, next_state) - -def simulate_threaded(grid): - next_grid = LockingGrid(grid.height, grid.width) - - threads = [] - for y in range(grid.height): - for x in range(grid.width): - args = (y, x, grid.get, next_grid.set) - thread = Thread(target=step_cell, args=args) - thread.start() # Fan out - threads.append(thread) - - for thread in threads: - thread.join() # Fan in - - return next_grid - - -# Example 3 -class ColumnPrinter: - def __init__(self): - self.columns = [] - - def append(self, data): - self.columns.append(data) - - def __str__(self): - row_count = 1 - for data in self.columns: - row_count = max( - row_count, len(data.splitlines()) + 1) - - rows = [''] * row_count - for j in range(row_count): - for i, data in enumerate(self.columns): - line = data.splitlines()[max(0, j - 1)] - if j == 0: - padding = ' ' * (len(line) // 2) - rows[j] += padding + str(i) + padding - else: - rows[j] += line - - if (i + 1) < len(self.columns): - rows[j] += ' | ' - - return '\n'.join(rows) - -grid = LockingGrid(5, 9) # Changed -grid.set(0, 3, ALIVE) -grid.set(1, 4, ALIVE) -grid.set(2, 2, ALIVE) -grid.set(2, 3, ALIVE) -grid.set(2, 4, ALIVE) - -columns = ColumnPrinter() -for i in range(5): - columns.append(str(grid)) - grid = simulate_threaded(grid) # Changed - -print(columns) - - -# Example 4 -def game_logic(state, neighbors): - raise OSError('Problem with I/O') - - -# Example 5 -import contextlib -import io - -fake_stderr = io.StringIO() -with contextlib.redirect_stderr(fake_stderr): - thread = Thread(target=game_logic, args=(ALIVE, 3)) - thread.start() - thread.join() - -print(fake_stderr.getvalue()) diff --git a/example_code/item_58.py b/example_code/item_58.py deleted file mode 100755 index e2b765d..0000000 --- a/example_code/item_58.py +++ /dev/null @@ -1,417 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -from queue import Queue - -class ClosableQueue(Queue): - SENTINEL = object() - - def close(self): - self.put(self.SENTINEL) - - def __iter__(self): - while True: - item = self.get() - try: - if item is self.SENTINEL: - return # Cause the thread to exit - yield item - finally: - self.task_done() - -in_queue = ClosableQueue() -out_queue = ClosableQueue() - - -# Example 2 -from threading import Thread - -class StoppableWorker(Thread): - def __init__(self, func, in_queue, out_queue, **kwargs): - super().__init__(**kwargs) - self.func = func - self.in_queue = in_queue - self.out_queue = out_queue - - def run(self): - for item in self.in_queue: - result = self.func(item) - self.out_queue.put(result) - -def game_logic(state, neighbors): - # Do some blocking input/output in here: - data = my_socket.recv(100) - -def game_logic(state, neighbors): - if state == ALIVE: - if neighbors < 2: - return EMPTY # Die: Too few - elif neighbors > 3: - return EMPTY # Die: Too many - else: - if neighbors == 3: - return ALIVE # Regenerate - return state - -def game_logic_thread(item): - y, x, state, neighbors = item - try: - next_state = game_logic(state, neighbors) - except Exception as e: - next_state = e - return (y, x, next_state) - -# Start the threads upfront -threads = [] -for _ in range(5): - thread = StoppableWorker( - game_logic_thread, in_queue, out_queue) - thread.start() - threads.append(thread) - - -# Example 3 -ALIVE = '*' -EMPTY = '-' - -class SimulationError(Exception): - pass - -class Grid: - def __init__(self, height, width): - self.height = height - self.width = width - self.rows = [] - for _ in range(self.height): - self.rows.append([EMPTY] * self.width) - - def get(self, y, x): - return self.rows[y % self.height][x % self.width] - - def set(self, y, x, state): - self.rows[y % self.height][x % self.width] = state - - def __str__(self): - output = '' - for row in self.rows: - for cell in row: - output += cell - output += '\n' - return output - -def count_neighbors(y, x, get): - n_ = get(y - 1, x + 0) # North - ne = get(y - 1, x + 1) # Northeast - e_ = get(y + 0, x + 1) # East - se = get(y + 1, x + 1) # Southeast - s_ = get(y + 1, x + 0) # South - sw = get(y + 1, x - 1) # Southwest - w_ = get(y + 0, x - 1) # West - nw = get(y - 1, x - 1) # Northwest - neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] - count = 0 - for state in neighbor_states: - if state == ALIVE: - count += 1 - return count - -def simulate_pipeline(grid, in_queue, out_queue): - for y in range(grid.height): - for x in range(grid.width): - state = grid.get(y, x) - neighbors = count_neighbors(y, x, grid.get) - in_queue.put((y, x, state, neighbors)) # Fan out - - in_queue.join() - out_queue.close() - - next_grid = Grid(grid.height, grid.width) - for item in out_queue: # Fan in - y, x, next_state = item - if isinstance(next_state, Exception): - raise SimulationError(y, x) from next_state - next_grid.set(y, x, next_state) - - return next_grid - - -# Example 4 -try: - def game_logic(state, neighbors): - raise OSError('Problem with I/O in game_logic') - - simulate_pipeline(Grid(1, 1), in_queue, out_queue) -except: - logging.exception('Expected') -else: - assert False - - -# Example 5 -# Clear the sentinel object from the out queue -for _ in out_queue: - pass - -# Restore the working version of this function -def game_logic(state, neighbors): - if state == ALIVE: - if neighbors < 2: - return EMPTY # Die: Too few - elif neighbors > 3: - return EMPTY # Die: Too many - else: - if neighbors == 3: - return ALIVE # Regenerate - return state - -class ColumnPrinter: - def __init__(self): - self.columns = [] - - def append(self, data): - self.columns.append(data) - - def __str__(self): - row_count = 1 - for data in self.columns: - row_count = max( - row_count, len(data.splitlines()) + 1) - - rows = [''] * row_count - for j in range(row_count): - for i, data in enumerate(self.columns): - line = data.splitlines()[max(0, j - 1)] - if j == 0: - padding = ' ' * (len(line) // 2) - rows[j] += padding + str(i) + padding - else: - rows[j] += line - - if (i + 1) < len(self.columns): - rows[j] += ' | ' - - return '\n'.join(rows) - -grid = Grid(5, 9) -grid.set(0, 3, ALIVE) -grid.set(1, 4, ALIVE) -grid.set(2, 2, ALIVE) -grid.set(2, 3, ALIVE) -grid.set(2, 4, ALIVE) - -columns = ColumnPrinter() -for i in range(5): - columns.append(str(grid)) - grid = simulate_pipeline(grid, in_queue, out_queue) - -print(columns) - -for thread in threads: - in_queue.close() -for thread in threads: - thread.join() - - -# Example 6 -def count_neighbors(y, x, get): - # Do some blocking input/output in here: - data = my_socket.recv(100) - - -# Example 7 -def count_neighbors(y, x, get): - n_ = get(y - 1, x + 0) # North - ne = get(y - 1, x + 1) # Northeast - e_ = get(y + 0, x + 1) # East - se = get(y + 1, x + 1) # Southeast - s_ = get(y + 1, x + 0) # South - sw = get(y + 1, x - 1) # Southwest - w_ = get(y + 0, x - 1) # West - nw = get(y - 1, x - 1) # Northwest - neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] - count = 0 - for state in neighbor_states: - if state == ALIVE: - count += 1 - return count - -def count_neighbors_thread(item): - y, x, state, get = item - try: - neighbors = count_neighbors(y, x, get) - except Exception as e: - neighbors = e - return (y, x, state, neighbors) - -def game_logic_thread(item): - y, x, state, neighbors = item - if isinstance(neighbors, Exception): - next_state = neighbors - else: - try: - next_state = game_logic(state, neighbors) - except Exception as e: - next_state = e - return (y, x, next_state) - -from threading import Lock - -class LockingGrid(Grid): - def __init__(self, height, width): - super().__init__(height, width) - self.lock = Lock() - - def __str__(self): - with self.lock: - return super().__str__() - - def get(self, y, x): - with self.lock: - return super().get(y, x) - - def set(self, y, x, state): - with self.lock: - return super().set(y, x, state) - - -# Example 8 -in_queue = ClosableQueue() -logic_queue = ClosableQueue() -out_queue = ClosableQueue() - -threads = [] - -for _ in range(5): - thread = StoppableWorker( - count_neighbors_thread, in_queue, logic_queue) - thread.start() - threads.append(thread) - -for _ in range(5): - thread = StoppableWorker( - game_logic_thread, logic_queue, out_queue) - thread.start() - threads.append(thread) - - -# Example 9 -def simulate_phased_pipeline( - grid, in_queue, logic_queue, out_queue): - for y in range(grid.height): - for x in range(grid.width): - state = grid.get(y, x) - item = (y, x, state, grid.get) - in_queue.put(item) # Fan out - - in_queue.join() - logic_queue.join() # Pipeline sequencing - out_queue.close() - - next_grid = LockingGrid(grid.height, grid.width) - for item in out_queue: # Fan in - y, x, next_state = item - if isinstance(next_state, Exception): - raise SimulationError(y, x) from next_state - next_grid.set(y, x, next_state) - - return next_grid - - -# Example 10 -grid = LockingGrid(5, 9) -grid.set(0, 3, ALIVE) -grid.set(1, 4, ALIVE) -grid.set(2, 2, ALIVE) -grid.set(2, 3, ALIVE) -grid.set(2, 4, ALIVE) - -columns = ColumnPrinter() -for i in range(5): - columns.append(str(grid)) - grid = simulate_phased_pipeline( - grid, in_queue, logic_queue, out_queue) - -print(columns) - -for thread in threads: - in_queue.close() -for thread in threads: - logic_queue.close() -for thread in threads: - thread.join() - - -# Example 11 -# Make sure exception propagation works as expected -def count_neighbors(*args): - raise OSError('Problem with I/O in count_neighbors') - -in_queue = ClosableQueue() -logic_queue = ClosableQueue() -out_queue = ClosableQueue() - -threads = [ - StoppableWorker( - count_neighbors_thread, in_queue, logic_queue, - daemon=True), - StoppableWorker( - game_logic_thread, logic_queue, out_queue, - daemon=True), -] - -for thread in threads: - thread.start() - -try: - simulate_phased_pipeline( - grid, in_queue, logic_queue, out_queue) -except SimulationError: - pass # Expected -else: - assert False diff --git a/example_code/item_59.py b/example_code/item_59.py deleted file mode 100755 index bd4ca10..0000000 --- a/example_code/item_59.py +++ /dev/null @@ -1,207 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -ALIVE = '*' -EMPTY = '-' - -class Grid: - def __init__(self, height, width): - self.height = height - self.width = width - self.rows = [] - for _ in range(self.height): - self.rows.append([EMPTY] * self.width) - - def get(self, y, x): - return self.rows[y % self.height][x % self.width] - - def set(self, y, x, state): - self.rows[y % self.height][x % self.width] = state - - def __str__(self): - output = '' - for row in self.rows: - for cell in row: - output += cell - output += '\n' - return output - -from threading import Lock - -class LockingGrid(Grid): - def __init__(self, height, width): - super().__init__(height, width) - self.lock = Lock() - - def __str__(self): - with self.lock: - return super().__str__() - - def get(self, y, x): - with self.lock: - return super().get(y, x) - - def set(self, y, x, state): - with self.lock: - return super().set(y, x, state) - -def count_neighbors(y, x, get): - n_ = get(y - 1, x + 0) # North - ne = get(y - 1, x + 1) # Northeast - e_ = get(y + 0, x + 1) # East - se = get(y + 1, x + 1) # Southeast - s_ = get(y + 1, x + 0) # South - sw = get(y + 1, x - 1) # Southwest - w_ = get(y + 0, x - 1) # West - nw = get(y - 1, x - 1) # Northwest - neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] - count = 0 - for state in neighbor_states: - if state == ALIVE: - count += 1 - return count - -def game_logic(state, neighbors): - # Do some blocking input/output in here: - data = my_socket.recv(100) - -def game_logic(state, neighbors): - if state == ALIVE: - if neighbors < 2: - return EMPTY # Die: Too few - elif neighbors > 3: - return EMPTY # Die: Too many - else: - if neighbors == 3: - return ALIVE # Regenerate - return state - -def step_cell(y, x, get, set): - state = get(y, x) - neighbors = count_neighbors(y, x, get) - next_state = game_logic(state, neighbors) - set(y, x, next_state) - - -# Example 2 -from concurrent.futures import ThreadPoolExecutor - -def simulate_pool(pool, grid): - next_grid = LockingGrid(grid.height, grid.width) - - futures = [] - for y in range(grid.height): - for x in range(grid.width): - args = (y, x, grid.get, next_grid.set) - future = pool.submit(step_cell, *args) # Fan out - futures.append(future) - - for future in futures: - future.result() # Fan in - - return next_grid - - -# Example 3 -class ColumnPrinter: - def __init__(self): - self.columns = [] - - def append(self, data): - self.columns.append(data) - - def __str__(self): - row_count = 1 - for data in self.columns: - row_count = max( - row_count, len(data.splitlines()) + 1) - - rows = [''] * row_count - for j in range(row_count): - for i, data in enumerate(self.columns): - line = data.splitlines()[max(0, j - 1)] - if j == 0: - padding = ' ' * (len(line) // 2) - rows[j] += padding + str(i) + padding - else: - rows[j] += line - - if (i + 1) < len(self.columns): - rows[j] += ' | ' - - return '\n'.join(rows) - -grid = LockingGrid(5, 9) -grid.set(0, 3, ALIVE) -grid.set(1, 4, ALIVE) -grid.set(2, 2, ALIVE) -grid.set(2, 3, ALIVE) -grid.set(2, 4, ALIVE) - -columns = ColumnPrinter() -with ThreadPoolExecutor(max_workers=10) as pool: - for i in range(5): - columns.append(str(grid)) - grid = simulate_pool(pool, grid) - -print(columns) - - -# Example 4 -try: - def game_logic(state, neighbors): - raise OSError('Problem with I/O') - - with ThreadPoolExecutor(max_workers=10) as pool: - task = pool.submit(game_logic, ALIVE, 3) - task.result() -except: - logging.exception('Expected') -else: - assert False diff --git a/example_code/item_60.py b/example_code/item_60.py deleted file mode 100755 index 5e1c0b4..0000000 --- a/example_code/item_60.py +++ /dev/null @@ -1,247 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -ALIVE = '*' -EMPTY = '-' - -class Grid: - def __init__(self, height, width): - self.height = height - self.width = width - self.rows = [] - for _ in range(self.height): - self.rows.append([EMPTY] * self.width) - - def get(self, y, x): - return self.rows[y % self.height][x % self.width] - - def set(self, y, x, state): - self.rows[y % self.height][x % self.width] = state - - def __str__(self): - output = '' - for row in self.rows: - for cell in row: - output += cell - output += '\n' - return output - -def count_neighbors(y, x, get): - n_ = get(y - 1, x + 0) # North - ne = get(y - 1, x + 1) # Northeast - e_ = get(y + 0, x + 1) # East - se = get(y + 1, x + 1) # Southeast - s_ = get(y + 1, x + 0) # South - sw = get(y + 1, x - 1) # Southwest - w_ = get(y + 0, x - 1) # West - nw = get(y - 1, x - 1) # Northwest - neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] - count = 0 - for state in neighbor_states: - if state == ALIVE: - count += 1 - return count - -async def game_logic(state, neighbors): - # Do some input/output in here: - data = await my_socket.read(50) - -async def game_logic(state, neighbors): - if state == ALIVE: - if neighbors < 2: - return EMPTY # Die: Too few - elif neighbors > 3: - return EMPTY # Die: Too many - else: - if neighbors == 3: - return ALIVE # Regenerate - return state - - -# Example 2 -async def step_cell(y, x, get, set): - state = get(y, x) - neighbors = count_neighbors(y, x, get) - next_state = await game_logic(state, neighbors) - set(y, x, next_state) - - -# Example 3 -import asyncio - -async def simulate(grid): - next_grid = Grid(grid.height, grid.width) - - tasks = [] - for y in range(grid.height): - for x in range(grid.width): - task = step_cell( - y, x, grid.get, next_grid.set) # Fan out - tasks.append(task) - - await asyncio.gather(*tasks) # Fan in - - return next_grid - - -# Example 4 -class ColumnPrinter: - def __init__(self): - self.columns = [] - - def append(self, data): - self.columns.append(data) - - def __str__(self): - row_count = 1 - for data in self.columns: - row_count = max( - row_count, len(data.splitlines()) + 1) - - rows = [''] * row_count - for j in range(row_count): - for i, data in enumerate(self.columns): - line = data.splitlines()[max(0, j - 1)] - if j == 0: - padding = ' ' * (len(line) // 2) - rows[j] += padding + str(i) + padding - else: - rows[j] += line - - if (i + 1) < len(self.columns): - rows[j] += ' | ' - - return '\n'.join(rows) - -logging.getLogger().setLevel(logging.ERROR) - -grid = Grid(5, 9) -grid.set(0, 3, ALIVE) -grid.set(1, 4, ALIVE) -grid.set(2, 2, ALIVE) -grid.set(2, 3, ALIVE) -grid.set(2, 4, ALIVE) - -columns = ColumnPrinter() -for i in range(5): - columns.append(str(grid)) - grid = asyncio.run(simulate(grid)) # Run the event loop - -print(columns) - -logging.getLogger().setLevel(logging.DEBUG) - - -# Example 5 -try: - async def game_logic(state, neighbors): - raise OSError('Problem with I/O') - - logging.getLogger().setLevel(logging.ERROR) - - asyncio.run(game_logic(ALIVE, 3)) - - logging.getLogger().setLevel(logging.DEBUG) -except: - logging.exception('Expected') -else: - assert False - - -# Example 6 -async def count_neighbors(y, x, get): - n_ = get(y - 1, x + 0) # North - ne = get(y - 1, x + 1) # Northeast - e_ = get(y + 0, x + 1) # East - se = get(y + 1, x + 1) # Southeast - s_ = get(y + 1, x + 0) # South - sw = get(y + 1, x - 1) # Southwest - w_ = get(y + 0, x - 1) # West - nw = get(y - 1, x - 1) # Northwest - neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] - count = 0 - for state in neighbor_states: - if state == ALIVE: - count += 1 - return count - -async def step_cell(y, x, get, set): - state = get(y, x) - neighbors = await count_neighbors(y, x, get) - next_state = await game_logic(state, neighbors) - set(y, x, next_state) - -async def game_logic(state, neighbors): - if state == ALIVE: - if neighbors < 2: - return EMPTY # Die: Too few - elif neighbors > 3: - return EMPTY # Die: Too many - else: - if neighbors == 3: - return ALIVE # Regenerate - return state - -logging.getLogger().setLevel(logging.ERROR) - -grid = Grid(5, 9) -grid.set(0, 3, ALIVE) -grid.set(1, 4, ALIVE) -grid.set(2, 2, ALIVE) -grid.set(2, 3, ALIVE) -grid.set(2, 4, ALIVE) - -columns = ColumnPrinter() -for i in range(5): - columns.append(str(grid)) - grid = asyncio.run(simulate(grid)) - -print(columns) - -logging.getLogger().setLevel(logging.DEBUG) diff --git a/example_code/item_61.py b/example_code/item_61.py deleted file mode 100755 index 96ed4bb..0000000 --- a/example_code/item_61.py +++ /dev/null @@ -1,454 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class EOFError(Exception): - pass - -class ConnectionBase: - def __init__(self, connection): - self.connection = connection - self.file = connection.makefile('rb') - - def send(self, command): - line = command + '\n' - data = line.encode() - self.connection.send(data) - - def receive(self): - line = self.file.readline() - if not line: - raise EOFError('Connection closed') - return line[:-1].decode() - - -# Example 2 -import random - -WARMER = 'Warmer' -COLDER = 'Colder' -UNSURE = 'Unsure' -CORRECT = 'Correct' - -class UnknownCommandError(Exception): - pass - -class Session(ConnectionBase): - def __init__(self, *args): - super().__init__(*args) - self._clear_state(None, None) - - def _clear_state(self, lower, upper): - self.lower = lower - self.upper = upper - self.secret = None - self.guesses = [] - - -# Example 3 - def loop(self): - while command := self.receive(): - parts = command.split(' ') - if parts[0] == 'PARAMS': - self.set_params(parts) - elif parts[0] == 'NUMBER': - self.send_number() - elif parts[0] == 'REPORT': - self.receive_report(parts) - else: - raise UnknownCommandError(command) - - -# Example 4 - def set_params(self, parts): - assert len(parts) == 3 - lower = int(parts[1]) - upper = int(parts[2]) - self._clear_state(lower, upper) - - -# Example 5 - def next_guess(self): - if self.secret is not None: - return self.secret - - while True: - guess = random.randint(self.lower, self.upper) - if guess not in self.guesses: - return guess - - def send_number(self): - guess = self.next_guess() - self.guesses.append(guess) - self.send(format(guess)) - - -# Example 6 - def receive_report(self, parts): - assert len(parts) == 2 - decision = parts[1] - - last = self.guesses[-1] - if decision == CORRECT: - self.secret = last - - print(f'Server: {last} is {decision}') - - -# Example 7 -import contextlib -import math - -class Client(ConnectionBase): - def __init__(self, *args): - super().__init__(*args) - self._clear_state() - - def _clear_state(self): - self.secret = None - self.last_distance = None - - -# Example 8 - @contextlib.contextmanager - def session(self, lower, upper, secret): - print(f'Guess a number between {lower} and {upper}!' - f' Shhhhh, it\'s {secret}.') - self.secret = secret - self.send(f'PARAMS {lower} {upper}') - try: - yield - finally: - self._clear_state() - self.send('PARAMS 0 -1') - - -# Example 9 - def request_numbers(self, count): - for _ in range(count): - self.send('NUMBER') - data = self.receive() - yield int(data) - if self.last_distance == 0: - return - - -# Example 10 - def report_outcome(self, number): - new_distance = math.fabs(number - self.secret) - decision = UNSURE - - if new_distance == 0: - decision = CORRECT - elif self.last_distance is None: - pass - elif new_distance < self.last_distance: - decision = WARMER - elif new_distance > self.last_distance: - decision = COLDER - - self.last_distance = new_distance - - self.send(f'REPORT {decision}') - return decision - - -# Example 11 -import socket -from threading import Thread - -def handle_connection(connection): - with connection: - session = Session(connection) - try: - session.loop() - except EOFError: - pass - -def run_server(address): - with socket.socket() as listener: - # Allow the port to be reused - listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - listener.bind(address) - listener.listen() - while True: - connection, _ = listener.accept() - thread = Thread(target=handle_connection, - args=(connection,), - daemon=True) - thread.start() - - -# Example 12 -def run_client(address): - with socket.create_connection(address) as connection: - client = Client(connection) - - with client.session(1, 5, 3): - results = [(x, client.report_outcome(x)) - for x in client.request_numbers(5)] - - with client.session(10, 15, 12): - for number in client.request_numbers(5): - outcome = client.report_outcome(number) - results.append((number, outcome)) - - return results - - -# Example 13 -def main(): - address = ('127.0.0.1', 1234) - server_thread = Thread( - target=run_server, args=(address,), daemon=True) - server_thread.start() - - results = run_client(address) - for number, outcome in results: - print(f'Client: {number} is {outcome}') - -main() - - -# Example 14 -class AsyncConnectionBase: - def __init__(self, reader, writer): # Changed - self.reader = reader # Changed - self.writer = writer # Changed - - async def send(self, command): - line = command + '\n' - data = line.encode() - self.writer.write(data) # Changed - await self.writer.drain() # Changed - - async def receive(self): - line = await self.reader.readline() # Changed - if not line: - raise EOFError('Connection closed') - return line[:-1].decode() - - -# Example 15 -class AsyncSession(AsyncConnectionBase): # Changed - def __init__(self, *args): - super().__init__(*args) - self._clear_values(None, None) - - def _clear_values(self, lower, upper): - self.lower = lower - self.upper = upper - self.secret = None - self.guesses = [] - - -# Example 16 - async def loop(self): # Changed - while command := await self.receive(): # Changed - parts = command.split(' ') - if parts[0] == 'PARAMS': - self.set_params(parts) - elif parts[0] == 'NUMBER': - await self.send_number() # Changed - elif parts[0] == 'REPORT': - self.receive_report(parts) - else: - raise UnknownCommandError(command) - - -# Example 17 - def set_params(self, parts): - assert len(parts) == 3 - lower = int(parts[1]) - upper = int(parts[2]) - self._clear_values(lower, upper) - - -# Example 18 - def next_guess(self): - if self.secret is not None: - return self.secret - - while True: - guess = random.randint(self.lower, self.upper) - if guess not in self.guesses: - return guess - - async def send_number(self): # Changed - guess = self.next_guess() - self.guesses.append(guess) - await self.send(format(guess)) # Changed - - -# Example 19 - def receive_report(self, parts): - assert len(parts) == 2 - decision = parts[1] - - last = self.guesses[-1] - if decision == CORRECT: - self.secret = last - - print(f'Server: {last} is {decision}') - - -# Example 20 -class AsyncClient(AsyncConnectionBase): # Changed - def __init__(self, *args): - super().__init__(*args) - self._clear_state() - - def _clear_state(self): - self.secret = None - self.last_distance = None - - -# Example 21 - @contextlib.asynccontextmanager # Changed - async def session(self, lower, upper, secret): # Changed - print(f'Guess a number between {lower} and {upper}!' - f' Shhhhh, it\'s {secret}.') - self.secret = secret - await self.send(f'PARAMS {lower} {upper}') # Changed - try: - yield - finally: - self._clear_state() - await self.send('PARAMS 0 -1') # Changed - - -# Example 22 - async def request_numbers(self, count): # Changed - for _ in range(count): - await self.send('NUMBER') # Changed - data = await self.receive() # Changed - yield int(data) - if self.last_distance == 0: - return - - -# Example 23 - async def report_outcome(self, number): # Changed - new_distance = math.fabs(number - self.secret) - decision = UNSURE - - if new_distance == 0: - decision = CORRECT - elif self.last_distance is None: - pass - elif new_distance < self.last_distance: - decision = WARMER - elif new_distance > self.last_distance: - decision = COLDER - - self.last_distance = new_distance - - await self.send(f'REPORT {decision}') # Changed - # Make it so the output printing is in - # the same order as the threaded version. - await asyncio.sleep(0.01) - return decision - - -# Example 24 -import asyncio - -async def handle_async_connection(reader, writer): - session = AsyncSession(reader, writer) - try: - await session.loop() - except EOFError: - pass - -async def run_async_server(address): - server = await asyncio.start_server( - handle_async_connection, *address) - async with server: - await server.serve_forever() - - -# Example 25 -async def run_async_client(address): - # Wait for the server to listen before trying to connect - await asyncio.sleep(0.1) - - streams = await asyncio.open_connection(*address) # New - client = AsyncClient(*streams) # New - - async with client.session(1, 5, 3): - results = [(x, await client.report_outcome(x)) - async for x in client.request_numbers(5)] - - async with client.session(10, 15, 12): - async for number in client.request_numbers(5): - outcome = await client.report_outcome(number) - results.append((number, outcome)) - - _, writer = streams # New - writer.close() # New - await writer.wait_closed() # New - - return results - - -# Example 26 -async def main_async(): - address = ('127.0.0.1', 4321) - - server = run_async_server(address) - asyncio.create_task(server) - - results = await run_async_client(address) - for number, outcome in results: - print(f'Client: {number} is {outcome}') - -logging.getLogger().setLevel(logging.ERROR) - -asyncio.run(main_async()) - -logging.getLogger().setLevel(logging.DEBUG) diff --git a/example_code/item_62.py b/example_code/item_62.py deleted file mode 100755 index d94a1f4..0000000 --- a/example_code/item_62.py +++ /dev/null @@ -1,295 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class NoNewData(Exception): - pass - -def readline(handle): - offset = handle.tell() - handle.seek(0, 2) - length = handle.tell() - - if length == offset: - raise NoNewData - - handle.seek(offset, 0) - return handle.readline() - - -# Example 2 -import time - -def tail_file(handle, interval, write_func): - while not handle.closed: - try: - line = readline(handle) - except NoNewData: - time.sleep(interval) - else: - write_func(line) - - -# Example 3 -from threading import Lock, Thread - -def run_threads(handles, interval, output_path): - with open(output_path, 'wb') as output: - lock = Lock() - def write(data): - with lock: - output.write(data) - - threads = [] - for handle in handles: - args = (handle, interval, write) - thread = Thread(target=tail_file, args=args) - thread.start() - threads.append(thread) - - for thread in threads: - thread.join() - - -# Example 4 -# This is all code to simulate the writers to the handles -import collections -import os -import random -import string -from tempfile import TemporaryDirectory - -def write_random_data(path, write_count, interval): - with open(path, 'wb') as f: - for i in range(write_count): - time.sleep(random.random() * interval) - letters = random.choices( - string.ascii_lowercase, k=10) - data = f'{path}-{i:02}-{"".join(letters)}\n' - f.write(data.encode()) - f.flush() - -def start_write_threads(directory, file_count): - paths = [] - for i in range(file_count): - path = os.path.join(directory, str(i)) - with open(path, 'w'): - # Make sure the file at this path will exist when - # the reading thread tries to poll it. - pass - paths.append(path) - args = (path, 10, 0.1) - thread = Thread(target=write_random_data, args=args) - thread.start() - return paths - -def close_all(handles): - time.sleep(1) - for handle in handles: - handle.close() - -def setup(): - tmpdir = TemporaryDirectory() - input_paths = start_write_threads(tmpdir.name, 5) - - handles = [] - for path in input_paths: - handle = open(path, 'rb') - handles.append(handle) - - Thread(target=close_all, args=(handles,)).start() - - output_path = os.path.join(tmpdir.name, 'merged') - return tmpdir, input_paths, handles, output_path - - -# Example 5 -def confirm_merge(input_paths, output_path): - found = collections.defaultdict(list) - with open(output_path, 'rb') as f: - for line in f: - for path in input_paths: - if line.find(path.encode()) == 0: - found[path].append(line) - - expected = collections.defaultdict(list) - for path in input_paths: - with open(path, 'rb') as f: - expected[path].extend(f.readlines()) - - for key, expected_lines in expected.items(): - found_lines = found[key] - assert expected_lines == found_lines, \ - f'{expected_lines!r} == {found_lines!r}' - -input_paths = ... -handles = ... -output_path = ... - -tmpdir, input_paths, handles, output_path = setup() - -run_threads(handles, 0.1, output_path) - -confirm_merge(input_paths, output_path) - -tmpdir.cleanup() - - -# Example 6 -import asyncio - -# On Windows, a ProactorEventLoop can't be created within -# threads because it tries to register signal handlers. This -# is a work-around to always use the SelectorEventLoop policy -# instead. See: https://bugs.python.org/issue33792 -policy = asyncio.get_event_loop_policy() -policy._loop_factory = asyncio.SelectorEventLoop - -async def run_tasks_mixed(handles, interval, output_path): - loop = asyncio.get_event_loop() - - with open(output_path, 'wb') as output: - async def write_async(data): - output.write(data) - - def write(data): - coro = write_async(data) - future = asyncio.run_coroutine_threadsafe( - coro, loop) - future.result() - - tasks = [] - for handle in handles: - task = loop.run_in_executor( - None, tail_file, handle, interval, write) - tasks.append(task) - - await asyncio.gather(*tasks) - - -# Example 7 -input_paths = ... -handles = ... -output_path = ... - -tmpdir, input_paths, handles, output_path = setup() - -asyncio.run(run_tasks_mixed(handles, 0.1, output_path)) - -confirm_merge(input_paths, output_path) - -tmpdir.cleanup() - - -# Example 8 -async def tail_async(handle, interval, write_func): - loop = asyncio.get_event_loop() - - while not handle.closed: - try: - line = await loop.run_in_executor( - None, readline, handle) - except NoNewData: - await asyncio.sleep(interval) - else: - await write_func(line) - - -# Example 9 -async def run_tasks(handles, interval, output_path): - with open(output_path, 'wb') as output: - async def write_async(data): - output.write(data) - - tasks = [] - for handle in handles: - coro = tail_async(handle, interval, write_async) - task = asyncio.create_task(coro) - tasks.append(task) - - await asyncio.gather(*tasks) - - -# Example 10 -input_paths = ... -handles = ... -output_path = ... - -tmpdir, input_paths, handles, output_path = setup() - -asyncio.run(run_tasks(handles, 0.1, output_path)) - -confirm_merge(input_paths, output_path) - -tmpdir.cleanup() - - -# Example 11 -def tail_file(handle, interval, write_func): - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - - async def write_async(data): - write_func(data) - - coro = tail_async(handle, interval, write_async) - loop.run_until_complete(coro) - - -# Example 12 -input_paths = ... -handles = ... -output_path = ... - -tmpdir, input_paths, handles, output_path = setup() - -run_threads(handles, 0.1, output_path) - -confirm_merge(input_paths, output_path) - -tmpdir.cleanup() diff --git a/example_code/item_63.py b/example_code/item_63.py deleted file mode 100755 index 5a56e96..0000000 --- a/example_code/item_63.py +++ /dev/null @@ -1,252 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -import asyncio - -# On Windows, a ProactorEventLoop can't be created within -# threads because it tries to register signal handlers. This -# is a work-around to always use the SelectorEventLoop policy -# instead. See: https://bugs.python.org/issue33792 -policy = asyncio.get_event_loop_policy() -policy._loop_factory = asyncio.SelectorEventLoop - -async def run_tasks(handles, interval, output_path): - with open(output_path, 'wb') as output: - async def write_async(data): - output.write(data) - - tasks = [] - for handle in handles: - coro = tail_async(handle, interval, write_async) - task = asyncio.create_task(coro) - tasks.append(task) - - await asyncio.gather(*tasks) - - -# Example 2 -import time - -async def slow_coroutine(): - time.sleep(0.5) # Simulating slow I/O - -asyncio.run(slow_coroutine(), debug=True) - - -# Example 3 -from threading import Thread - -class WriteThread(Thread): - def __init__(self, output_path): - super().__init__() - self.output_path = output_path - self.output = None - self.loop = asyncio.new_event_loop() - - def run(self): - asyncio.set_event_loop(self.loop) - with open(self.output_path, 'wb') as self.output: - self.loop.run_forever() - - # Run one final round of callbacks so the await on - # stop() in another event loop will be resolved. - self.loop.run_until_complete(asyncio.sleep(0)) - - -# Example 4 - async def real_write(self, data): - self.output.write(data) - - async def write(self, data): - coro = self.real_write(data) - future = asyncio.run_coroutine_threadsafe( - coro, self.loop) - await asyncio.wrap_future(future) - - -# Example 5 - async def real_stop(self): - self.loop.stop() - - async def stop(self): - coro = self.real_stop() - future = asyncio.run_coroutine_threadsafe( - coro, self.loop) - await asyncio.wrap_future(future) - - -# Example 6 - async def __aenter__(self): - loop = asyncio.get_event_loop() - await loop.run_in_executor(None, self.start) - return self - - async def __aexit__(self, *_): - await self.stop() - - -# Example 7 -class NoNewData(Exception): - pass - -def readline(handle): - offset = handle.tell() - handle.seek(0, 2) - length = handle.tell() - - if length == offset: - raise NoNewData - - handle.seek(offset, 0) - return handle.readline() - -async def tail_async(handle, interval, write_func): - loop = asyncio.get_event_loop() - - while not handle.closed: - try: - line = await loop.run_in_executor( - None, readline, handle) - except NoNewData: - await asyncio.sleep(interval) - else: - await write_func(line) - -async def run_fully_async(handles, interval, output_path): - async with WriteThread(output_path) as output: - tasks = [] - for handle in handles: - coro = tail_async(handle, interval, output.write) - task = asyncio.create_task(coro) - tasks.append(task) - - await asyncio.gather(*tasks) - - -# Example 8 -# This is all code to simulate the writers to the handles -import collections -import os -import random -import string -from tempfile import TemporaryDirectory - -def write_random_data(path, write_count, interval): - with open(path, 'wb') as f: - for i in range(write_count): - time.sleep(random.random() * interval) - letters = random.choices( - string.ascii_lowercase, k=10) - data = f'{path}-{i:02}-{"".join(letters)}\n' - f.write(data.encode()) - f.flush() - -def start_write_threads(directory, file_count): - paths = [] - for i in range(file_count): - path = os.path.join(directory, str(i)) - with open(path, 'w'): - # Make sure the file at this path will exist when - # the reading thread tries to poll it. - pass - paths.append(path) - args = (path, 10, 0.1) - thread = Thread(target=write_random_data, args=args) - thread.start() - return paths - -def close_all(handles): - time.sleep(1) - for handle in handles: - handle.close() - -def setup(): - tmpdir = TemporaryDirectory() - input_paths = start_write_threads(tmpdir.name, 5) - - handles = [] - for path in input_paths: - handle = open(path, 'rb') - handles.append(handle) - - Thread(target=close_all, args=(handles,)).start() - - output_path = os.path.join(tmpdir.name, 'merged') - return tmpdir, input_paths, handles, output_path - - -# Example 9 -def confirm_merge(input_paths, output_path): - found = collections.defaultdict(list) - with open(output_path, 'rb') as f: - for line in f: - for path in input_paths: - if line.find(path.encode()) == 0: - found[path].append(line) - - expected = collections.defaultdict(list) - for path in input_paths: - with open(path, 'rb') as f: - expected[path].extend(f.readlines()) - - for key, expected_lines in expected.items(): - found_lines = found[key] - assert expected_lines == found_lines - -input_paths = ... -handles = ... -output_path = ... - -tmpdir, input_paths, handles, output_path = setup() - -asyncio.run(run_fully_async(handles, 0.1, output_path)) - -confirm_merge(input_paths, output_path) - -tmpdir.cleanup() diff --git a/example_code/item_64/parallel/my_module.py b/example_code/item_64/parallel/my_module.py deleted file mode 100755 index 1027356..0000000 --- a/example_code/item_64/parallel/my_module.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -def gcd(pair): - a, b = pair - low = min(a, b) - for i in range(low, 0, -1): - if a % i == 0 and b % i == 0: - return i - assert False, 'Not reachable' diff --git a/example_code/item_64/parallel/run_parallel.py b/example_code/item_64/parallel/run_parallel.py deleted file mode 100755 index 92f6d46..0000000 --- a/example_code/item_64/parallel/run_parallel.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import my_module -from concurrent.futures import ProcessPoolExecutor -import time - -NUMBERS = [ - (1963309, 2265973), (2030677, 3814172), - (1551645, 2229620), (2039045, 2020802), - (1823712, 1924928), (2293129, 1020491), - (1281238, 2273782), (3823812, 4237281), - (3812741, 4729139), (1292391, 2123811), -] - -def main(): - start = time.time() - pool = ProcessPoolExecutor(max_workers=2) # The one change - results = list(pool.map(my_module.gcd, NUMBERS)) - end = time.time() - delta = end - start - print(f'Took {delta:.3f} seconds') - -if __name__ == '__main__': - main() diff --git a/example_code/item_64/parallel/run_serial.py b/example_code/item_64/parallel/run_serial.py deleted file mode 100755 index 4188579..0000000 --- a/example_code/item_64/parallel/run_serial.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import my_module -import time - -NUMBERS = [ - (1963309, 2265973), (2030677, 3814172), - (1551645, 2229620), (2039045, 2020802), - (1823712, 1924928), (2293129, 1020491), - (1281238, 2273782), (3823812, 4237281), - (3812741, 4729139), (1292391, 2123811), -] - -def main(): - start = time.time() - results = list(map(my_module.gcd, NUMBERS)) - end = time.time() - delta = end - start - print(f'Took {delta:.3f} seconds') - -if __name__ == '__main__': - main() diff --git a/example_code/item_64/parallel/run_threads.py b/example_code/item_64/parallel/run_threads.py deleted file mode 100755 index e89e78b..0000000 --- a/example_code/item_64/parallel/run_threads.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import my_module -from concurrent.futures import ThreadPoolExecutor -import time - -NUMBERS = [ - (1963309, 2265973), (2030677, 3814172), - (1551645, 2229620), (2039045, 2020802), - (1823712, 1924928), (2293129, 1020491), - (1281238, 2273782), (3823812, 4237281), - (3812741, 4729139), (1292391, 2123811), -] - -def main(): - start = time.time() - pool = ThreadPoolExecutor(max_workers=2) - results = list(pool.map(my_module.gcd, NUMBERS)) - end = time.time() - delta = end - start - print(f'Took {delta:.3f} seconds') - -if __name__ == '__main__': - main() diff --git a/example_code/item_65.py b/example_code/item_65.py deleted file mode 100755 index 8929b87..0000000 --- a/example_code/item_65.py +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -def try_finally_example(filename): - print('* Opening file') - handle = open(filename, encoding='utf-8') # May raise OSError - try: - print('* Reading data') - return handle.read() # May raise UnicodeDecodeError - finally: - print('* Calling close()') - handle.close() # Always runs after try block - - -# Example 2 -try: - filename = 'random_data.txt' - - with open(filename, 'wb') as f: - f.write(b'\xf1\xf2\xf3\xf4\xf5') # Invalid utf-8 - - data = try_finally_example(filename) - # This should not be reached. - import sys - sys.exit(1) -except: - logging.exception('Expected') -else: - assert False - - -# Example 3 -try: - try_finally_example('does_not_exist.txt') -except: - logging.exception('Expected') -else: - assert False - - -# Example 4 -import json - -def load_json_key(data, key): - try: - print('* Loading JSON data') - result_dict = json.loads(data) # May raise ValueError - except ValueError as e: - print('* Handling ValueError') - raise KeyError(key) from e - else: - print('* Looking up key') - return result_dict[key] # May raise KeyError - - -# Example 5 -assert load_json_key('{"foo": "bar"}', 'foo') == 'bar' - - -# Example 6 -try: - load_json_key('{"foo": bad payload', 'foo') -except: - logging.exception('Expected') -else: - assert False - - -# Example 7 -try: - load_json_key('{"foo": "bar"}', 'does not exist') -except: - logging.exception('Expected') -else: - assert False - - -# Example 8 -UNDEFINED = object() -DIE_IN_ELSE_BLOCK = False - -def divide_json(path): - print('* Opening file') - handle = open(path, 'r+') # May raise OSError - try: - print('* Reading data') - data = handle.read() # May raise UnicodeDecodeError - print('* Loading JSON data') - op = json.loads(data) # May raise ValueError - print('* Performing calculation') - value = ( - op['numerator'] / - op['denominator']) # May raise ZeroDivisionError - except ZeroDivisionError as e: - print('* Handling ZeroDivisionError') - return UNDEFINED - else: - print('* Writing calculation') - op['result'] = value - result = json.dumps(op) - handle.seek(0) # May raise OSError - if DIE_IN_ELSE_BLOCK: - import errno - import os - raise OSError(errno.ENOSPC, os.strerror(errno.ENOSPC)) - handle.write(result) # May raise OSError - return value - finally: - print('* Calling close()') - handle.close() # Always runs - - -# Example 9 -temp_path = 'random_data.json' - -with open(temp_path, 'w') as f: - f.write('{"numerator": 1, "denominator": 10}') - -assert divide_json(temp_path) == 0.1 - - -# Example 10 -with open(temp_path, 'w') as f: - f.write('{"numerator": 1, "denominator": 0}') - -assert divide_json(temp_path) is UNDEFINED - - -# Example 11 -try: - with open(temp_path, 'w') as f: - f.write('{"numerator": 1 bad data') - - divide_json(temp_path) -except: - logging.exception('Expected') -else: - assert False - - -# Example 12 -try: - with open(temp_path, 'w') as f: - f.write('{"numerator": 1, "denominator": 10}') - DIE_IN_ELSE_BLOCK = True - - divide_json(temp_path) -except: - logging.exception('Expected') -else: - assert False diff --git a/example_code/item_66.py b/example_code/item_66.py deleted file mode 100755 index 213d70a..0000000 --- a/example_code/item_66.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -from threading import Lock - -lock = Lock() -with lock: - # Do something while maintaining an invariant - pass - - -# Example 2 -lock.acquire() -try: - # Do something while maintaining an invariant - pass -finally: - lock.release() - - -# Example 3 -import logging -logging.getLogger().setLevel(logging.WARNING) - -def my_function(): - logging.debug('Some debug data') - logging.error('Error log here') - logging.debug('More debug data') - - -# Example 4 -my_function() - - -# Example 5 -from contextlib import contextmanager - -@contextmanager -def debug_logging(level): - logger = logging.getLogger() - old_level = logger.getEffectiveLevel() - logger.setLevel(level) - try: - yield - finally: - logger.setLevel(old_level) - - -# Example 6 -with debug_logging(logging.DEBUG): - print('* Inside:') - my_function() - -print('* After:') -my_function() - - -# Example 7 -with open('my_output.txt', 'w') as handle: - handle.write('This is some data!') - - -# Example 8 -@contextmanager -def log_level(level, name): - logger = logging.getLogger(name) - old_level = logger.getEffectiveLevel() - logger.setLevel(level) - try: - yield logger - finally: - logger.setLevel(old_level) - - -# Example 9 -with log_level(logging.DEBUG, 'my-log') as logger: - logger.debug(f'This is a message for {logger.name}!') - logging.debug('This will not print') - - -# Example 10 -logger = logging.getLogger('my-log') -logger.debug('Debug will not print') -logger.error('Error will print') - - -# Example 11 -with log_level(logging.DEBUG, 'other-log') as logger: - logger.debug(f'This is a message for {logger.name}!') - logging.debug('This will not print') diff --git a/example_code/item_67.py b/example_code/item_67.py deleted file mode 100755 index 1a75166..0000000 --- a/example_code/item_67.py +++ /dev/null @@ -1,125 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -import time - -now = 1552774475 -local_tuple = time.localtime(now) -time_format = '%Y-%m-%d %H:%M:%S' -time_str = time.strftime(time_format, local_tuple) -print(time_str) - - -# Example 2 -time_tuple = time.strptime(time_str, time_format) -utc_now = time.mktime(time_tuple) -print(utc_now) - - -# Example 3 -import os - -if os.name == 'nt': - print("This example doesn't work on Windows") -else: - parse_format = '%Y-%m-%d %H:%M:%S %Z' - depart_sfo = '2019-03-16 15:45:16 PDT' - time_tuple = time.strptime(depart_sfo, parse_format) - time_str = time.strftime(time_format, time_tuple) - print(time_str) - - -# Example 4 -try: - arrival_nyc = '2019-03-16 23:33:24 EDT' - time_tuple = time.strptime(arrival_nyc, time_format) -except: - logging.exception('Expected') -else: - assert False - - -# Example 5 -from datetime import datetime, timezone - -now = datetime(2019, 3, 16, 22, 14, 35) -now_utc = now.replace(tzinfo=timezone.utc) -now_local = now_utc.astimezone() -print(now_local) - - -# Example 6 -time_str = '2019-03-16 15:14:35' -now = datetime.strptime(time_str, time_format) -time_tuple = now.timetuple() -utc_now = time.mktime(time_tuple) -print(utc_now) - - -# Example 7 -import pytz - -arrival_nyc = '2019-03-16 23:33:24' -nyc_dt_naive = datetime.strptime(arrival_nyc, time_format) -eastern = pytz.timezone('US/Eastern') -nyc_dt = eastern.localize(nyc_dt_naive) -utc_dt = pytz.utc.normalize(nyc_dt.astimezone(pytz.utc)) -print(utc_dt) - - -# Example 8 -pacific = pytz.timezone('US/Pacific') -sf_dt = pacific.normalize(utc_dt.astimezone(pacific)) -print(sf_dt) - - -# Example 9 -nepal = pytz.timezone('Asia/Katmandu') -nepal_dt = nepal.normalize(utc_dt.astimezone(nepal)) -print(nepal_dt) diff --git a/example_code/item_68.py b/example_code/item_68.py deleted file mode 100755 index 4b9eb5f..0000000 --- a/example_code/item_68.py +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class GameState: - def __init__(self): - self.level = 0 - self.lives = 4 - - -# Example 2 -state = GameState() -state.level += 1 # Player beat a level -state.lives -= 1 # Player had to try again - -print(state.__dict__) - - -# Example 3 -import pickle - -state_path = 'game_state.bin' -with open(state_path, 'wb') as f: - pickle.dump(state, f) - - -# Example 4 -with open(state_path, 'rb') as f: - state_after = pickle.load(f) - -print(state_after.__dict__) - - -# Example 5 -class GameState: - def __init__(self): - self.level = 0 - self.lives = 4 - self.points = 0 # New field - - -# Example 6 -state = GameState() -serialized = pickle.dumps(state) -state_after = pickle.loads(serialized) -print(state_after.__dict__) - - -# Example 7 -with open(state_path, 'rb') as f: - state_after = pickle.load(f) - -print(state_after.__dict__) - - -# Example 8 -assert isinstance(state_after, GameState) - - -# Example 9 -class GameState: - def __init__(self, level=0, lives=4, points=0): - self.level = level - self.lives = lives - self.points = points - - -# Example 10 -def pickle_game_state(game_state): - kwargs = game_state.__dict__ - return unpickle_game_state, (kwargs,) - - -# Example 11 -def unpickle_game_state(kwargs): - return GameState(**kwargs) - - -# Example 12 -import copyreg - -copyreg.pickle(GameState, pickle_game_state) - - -# Example 13 -state = GameState() -state.points += 1000 -serialized = pickle.dumps(state) -state_after = pickle.loads(serialized) -print(state_after.__dict__) - - -# Example 14 -class GameState: - def __init__(self, level=0, lives=4, points=0, magic=5): - self.level = level - self.lives = lives - self.points = points - self.magic = magic # New field - - -# Example 15 -print('Before:', state.__dict__) -state_after = pickle.loads(serialized) -print('After: ', state_after.__dict__) - - -# Example 16 -class GameState: - def __init__(self, level=0, points=0, magic=5): - self.level = level - self.points = points - self.magic = magic - - -# Example 17 -try: - pickle.loads(serialized) -except: - logging.exception('Expected') -else: - assert False - - -# Example 18 -def pickle_game_state(game_state): - kwargs = game_state.__dict__ - kwargs['version'] = 2 - return unpickle_game_state, (kwargs,) - - -# Example 19 -def unpickle_game_state(kwargs): - version = kwargs.pop('version', 1) - if version == 1: - del kwargs['lives'] - return GameState(**kwargs) - - -# Example 20 -copyreg.pickle(GameState, pickle_game_state) -print('Before:', state.__dict__) -state_after = pickle.loads(serialized) -print('After: ', state_after.__dict__) - - -# Example 21 -copyreg.dispatch_table.clear() -state = GameState() -serialized = pickle.dumps(state) -del GameState -class BetterGameState: - def __init__(self, level=0, points=0, magic=5): - self.level = level - self.points = points - self.magic = magic - - -# Example 22 -try: - pickle.loads(serialized) -except: - logging.exception('Expected') -else: - assert False - - -# Example 23 -print(serialized) - - -# Example 24 -copyreg.pickle(BetterGameState, pickle_game_state) - - -# Example 25 -state = BetterGameState() -serialized = pickle.dumps(state) -print(serialized) diff --git a/example_code/item_69.py b/example_code/item_69.py deleted file mode 100755 index 1e18676..0000000 --- a/example_code/item_69.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -rate = 1.45 -seconds = 3*60 + 42 -cost = rate * seconds / 60 -print(cost) - - -# Example 2 -print(round(cost, 2)) - - -# Example 3 -from decimal import Decimal - -rate = Decimal('1.45') -seconds = Decimal(3*60 + 42) -cost = rate * seconds / Decimal(60) -print(cost) - - -# Example 4 -print(Decimal('1.45')) -print(Decimal(1.45)) - - -# Example 5 -print('456') -print(456) - - -# Example 6 -rate = Decimal('0.05') -seconds = Decimal('5') -small_cost = rate * seconds / Decimal(60) -print(small_cost) - - -# Example 7 -print(round(small_cost, 2)) - - -# Example 8 -from decimal import ROUND_UP - -rounded = cost.quantize(Decimal('0.01'), rounding=ROUND_UP) -print(f'Rounded {cost} to {rounded}') - - -# Example 9 -rounded = small_cost.quantize(Decimal('0.01'), rounding=ROUND_UP) -print(f'Rounded {small_cost} to {rounded}') diff --git a/example_code/item_70.py b/example_code/item_70.py deleted file mode 100755 index 3775662..0000000 --- a/example_code/item_70.py +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -def insertion_sort(data): - result = [] - for value in data: - insert_value(result, value) - return result - - -# Example 2 -def insert_value(array, value): - for i, existing in enumerate(array): - if existing > value: - array.insert(i, value) - return - array.append(value) - - -# Example 3 -from random import randint - -max_size = 10**4 -data = [randint(0, max_size) for _ in range(max_size)] -test = lambda: insertion_sort(data) - - -# Example 4 -from cProfile import Profile - -profiler = Profile() -profiler.runcall(test) - - -# Example 5 -from pstats import Stats - -stats = Stats(profiler) -stats = Stats(profiler, stream=STDOUT) -stats.strip_dirs() -stats.sort_stats('cumulative') -stats.print_stats() - - -# Example 6 -from bisect import bisect_left - -def insert_value(array, value): - i = bisect_left(array, value) - array.insert(i, value) - - -# Example 7 -profiler = Profile() -profiler.runcall(test) -stats = Stats(profiler, stream=STDOUT) -stats.strip_dirs() -stats.sort_stats('cumulative') -stats.print_stats() - - -# Example 8 -def my_utility(a, b): - c = 1 - for i in range(100): - c += a * b - -def first_func(): - for _ in range(1000): - my_utility(4, 5) - -def second_func(): - for _ in range(10): - my_utility(1, 3) - -def my_program(): - for _ in range(20): - first_func() - second_func() - - -# Example 9 -profiler = Profile() -profiler.runcall(my_program) -stats = Stats(profiler, stream=STDOUT) -stats.strip_dirs() -stats.sort_stats('cumulative') -stats.print_stats() - - -# Example 10 -stats = Stats(profiler, stream=STDOUT) -stats.strip_dirs() -stats.sort_stats('cumulative') -stats.print_callers() diff --git a/example_code/item_71.py b/example_code/item_71.py deleted file mode 100755 index bf16a0a..0000000 --- a/example_code/item_71.py +++ /dev/null @@ -1,264 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class Email: - def __init__(self, sender, receiver, message): - self.sender = sender - self.receiver = receiver - self.message = message - - -# Example 2 -def get_emails(): - yield Email('foo@example.com', 'bar@example.com', 'hello1') - yield Email('baz@example.com', 'banana@example.com', 'hello2') - yield None - yield Email('meep@example.com', 'butter@example.com', 'hello3') - yield Email('stuff@example.com', 'avocado@example.com', 'hello4') - yield None - yield Email('thingy@example.com', 'orange@example.com', 'hello5') - yield Email('roger@example.com', 'bob@example.com', 'hello6') - yield None - yield Email('peanut@example.com', 'alice@example.com', 'hello7') - yield None - -EMAIL_IT = get_emails() - -class NoEmailError(Exception): - pass - -def try_receive_email(): - # Returns an Email instance or raises NoEmailError - try: - email = next(EMAIL_IT) - except StopIteration: - email = None - - if not email: - raise NoEmailError - - print(f'Produced email: {email.message}') - return email - - -# Example 3 -def produce_emails(queue): - while True: - try: - email = try_receive_email() - except NoEmailError: - return - else: - queue.append(email) # Producer - - -# Example 4 -def consume_one_email(queue): - if not queue: - return - email = queue.pop(0) # Consumer - # Index the message for long-term archival - print(f'Consumed email: {email.message}') - - -# Example 5 -def loop(queue, keep_running): - while keep_running(): - produce_emails(queue) - consume_one_email(queue) - -def make_test_end(): - count=list(range(10)) - - def func(): - if count: - count.pop() - return True - return False - - return func - - -def my_end_func(): - pass - -my_end_func = make_test_end() -loop([], my_end_func) - - -# Example 6 -import timeit - -def print_results(count, tests): - avg_iteration = sum(tests) / len(tests) - print(f'Count {count:>5,} takes {avg_iteration:.6f}s') - return count, avg_iteration - -def list_append_benchmark(count): - def run(queue): - for i in range(count): - queue.append(i) - - tests = timeit.repeat( - setup='queue = []', - stmt='run(queue)', - globals=locals(), - repeat=1000, - number=1) - - return print_results(count, tests) - - -# Example 7 -def print_delta(before, after): - before_count, before_time = before - after_count, after_time = after - growth = 1 + (after_count - before_count) / before_count - slowdown = 1 + (after_time - before_time) / before_time - print(f'{growth:>4.1f}x data size, {slowdown:>4.1f}x time') - -baseline = list_append_benchmark(500) -for count in (1_000, 2_000, 3_000, 4_000, 5_000): - print() - comparison = list_append_benchmark(count) - print_delta(baseline, comparison) - - -# Example 8 -def list_pop_benchmark(count): - def prepare(): - return list(range(count)) - - def run(queue): - while queue: - queue.pop(0) - - tests = timeit.repeat( - setup='queue = prepare()', - stmt='run(queue)', - globals=locals(), - repeat=1000, - number=1) - - return print_results(count, tests) - - -# Example 9 -baseline = list_pop_benchmark(500) -for count in (1_000, 2_000, 3_000, 4_000, 5_000): - print() - comparison = list_pop_benchmark(count) - print_delta(baseline, comparison) - - -# Example 10 -import collections - -def consume_one_email(queue): - if not queue: - return - email = queue.popleft() # Consumer - # Process the email message - print(f'Consumed email: {email.message}') - -def my_end_func(): - pass - -my_end_func = make_test_end() -EMAIL_IT = get_emails() -loop(collections.deque(), my_end_func) - - -# Example 11 -def deque_append_benchmark(count): - def prepare(): - return collections.deque() - - def run(queue): - for i in range(count): - queue.append(i) - - tests = timeit.repeat( - setup='queue = prepare()', - stmt='run(queue)', - globals=locals(), - repeat=1000, - number=1) - return print_results(count, tests) - -baseline = deque_append_benchmark(500) -for count in (1_000, 2_000, 3_000, 4_000, 5_000): - print() - comparison = deque_append_benchmark(count) - print_delta(baseline, comparison) - - -# Example 12 -def dequeue_popleft_benchmark(count): - def prepare(): - return collections.deque(range(count)) - - def run(queue): - while queue: - queue.popleft() - - tests = timeit.repeat( - setup='queue = prepare()', - stmt='run(queue)', - globals=locals(), - repeat=1000, - number=1) - - return print_results(count, tests) - -baseline = dequeue_popleft_benchmark(500) -for count in (1_000, 2_000, 3_000, 4_000, 5_000): - print() - comparison = dequeue_popleft_benchmark(count) - print_delta(baseline, comparison) diff --git a/example_code/item_72.py b/example_code/item_72.py deleted file mode 100755 index 078201a..0000000 --- a/example_code/item_72.py +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -data = list(range(10**5)) -index = data.index(91234) -assert index == 91234 - - -# Example 2 -def find_closest(sequence, goal): - for index, value in enumerate(sequence): - if goal < value: - return index - raise ValueError(f'{goal} is out of bounds') - -index = find_closest(data, 91234.56) -assert index == 91235 - -try: - find_closest(data, 100000000) -except ValueError: - pass # Expected -else: - assert False - - -# Example 3 -from bisect import bisect_left - -index = bisect_left(data, 91234) # Exact match -assert index == 91234 - -index = bisect_left(data, 91234.56) # Closest match -assert index == 91235 - - -# Example 4 -import random -import timeit - -size = 10**5 -iterations = 1000 - -data = list(range(size)) -to_lookup = [random.randint(0, size) - for _ in range(iterations)] - -def run_linear(data, to_lookup): - for index in to_lookup: - data.index(index) - -def run_bisect(data, to_lookup): - for index in to_lookup: - bisect_left(data, index) - -baseline = timeit.timeit( - stmt='run_linear(data, to_lookup)', - globals=globals(), - number=10) -print(f'Linear search takes {baseline:.6f}s') - -comparison = timeit.timeit( - stmt='run_bisect(data, to_lookup)', - globals=globals(), - number=10) -print(f'Bisect search takes {comparison:.6f}s') - -slowdown = 1 + ((baseline - comparison) / comparison) -print(f'{slowdown:.1f}x time') diff --git a/example_code/item_73.py b/example_code/item_73.py deleted file mode 100755 index 352d6ac..0000000 --- a/example_code/item_73.py +++ /dev/null @@ -1,384 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class Book: - def __init__(self, title, due_date): - self.title = title - self.due_date = due_date - - -# Example 2 -def add_book(queue, book): - queue.append(book) - queue.sort(key=lambda x: x.due_date, reverse=True) - -queue = [] -add_book(queue, Book('Don Quixote', '2019-06-07')) -add_book(queue, Book('Frankenstein', '2019-06-05')) -add_book(queue, Book('Les Misérables', '2019-06-08')) -add_book(queue, Book('War and Peace', '2019-06-03')) - - -# Example 3 -class NoOverdueBooks(Exception): - pass - -def next_overdue_book(queue, now): - if queue: - book = queue[-1] - if book.due_date < now: - queue.pop() - return book - - raise NoOverdueBooks - - -# Example 4 -now = '2019-06-10' - -found = next_overdue_book(queue, now) -print(found.title) - -found = next_overdue_book(queue, now) -print(found.title) - - -# Example 5 -def return_book(queue, book): - queue.remove(book) - -queue = [] -book = Book('Treasure Island', '2019-06-04') - -add_book(queue, book) -print('Before return:', [x.title for x in queue]) - -return_book(queue, book) -print('After return: ', [x.title for x in queue]) - - -# Example 6 -try: - next_overdue_book(queue, now) -except NoOverdueBooks: - pass # Expected -else: - assert False # Doesn't happen - - -# Example 7 -import random -import timeit - -def print_results(count, tests): - avg_iteration = sum(tests) / len(tests) - print(f'Count {count:>5,} takes {avg_iteration:.6f}s') - return count, avg_iteration - -def print_delta(before, after): - before_count, before_time = before - after_count, after_time = after - growth = 1 + (after_count - before_count) / before_count - slowdown = 1 + (after_time - before_time) / before_time - print(f'{growth:>4.1f}x data size, {slowdown:>4.1f}x time') - -def list_overdue_benchmark(count): - def prepare(): - to_add = list(range(count)) - random.shuffle(to_add) - return [], to_add - - def run(queue, to_add): - for i in to_add: - queue.append(i) - queue.sort(reverse=True) - - while queue: - queue.pop() - - tests = timeit.repeat( - setup='queue, to_add = prepare()', - stmt=f'run(queue, to_add)', - globals=locals(), - repeat=100, - number=1) - - return print_results(count, tests) - - -# Example 8 -baseline = list_overdue_benchmark(500) -for count in (1_000, 1_500, 2_000): - print() - comparison = list_overdue_benchmark(count) - print_delta(baseline, comparison) - - -# Example 9 -def list_return_benchmark(count): - def prepare(): - queue = list(range(count)) - random.shuffle(queue) - - to_return = list(range(count)) - random.shuffle(to_return) - - return queue, to_return - - def run(queue, to_return): - for i in to_return: - queue.remove(i) - - tests = timeit.repeat( - setup='queue, to_return = prepare()', - stmt=f'run(queue, to_return)', - globals=locals(), - repeat=100, - number=1) - - return print_results(count, tests) - - -# Example 10 -baseline = list_return_benchmark(500) -for count in (1_000, 1_500, 2_000): - print() - comparison = list_return_benchmark(count) - print_delta(baseline, comparison) - - -# Example 11 -from heapq import heappush - -def add_book(queue, book): - heappush(queue, book) - - -# Example 12 -try: - queue = [] - add_book(queue, Book('Little Women', '2019-06-05')) - add_book(queue, Book('The Time Machine', '2019-05-30')) -except: - logging.exception('Expected') -else: - assert False - - -# Example 13 -import functools - -@functools.total_ordering -class Book: - def __init__(self, title, due_date): - self.title = title - self.due_date = due_date - - def __lt__(self, other): - return self.due_date < other.due_date - - -# Example 14 -queue = [] -add_book(queue, Book('Pride and Prejudice', '2019-06-01')) -add_book(queue, Book('The Time Machine', '2019-05-30')) -add_book(queue, Book('Crime and Punishment', '2019-06-06')) -add_book(queue, Book('Wuthering Heights', '2019-06-12')) -print([b.title for b in queue]) - - -# Example 15 -queue = [ - Book('Pride and Prejudice', '2019-06-01'), - Book('The Time Machine', '2019-05-30'), - Book('Crime and Punishment', '2019-06-06'), - Book('Wuthering Heights', '2019-06-12'), -] -queue.sort() -print([b.title for b in queue]) - - -# Example 16 -from heapq import heapify - -queue = [ - Book('Pride and Prejudice', '2019-06-01'), - Book('The Time Machine', '2019-05-30'), - Book('Crime and Punishment', '2019-06-06'), - Book('Wuthering Heights', '2019-06-12'), -] -heapify(queue) -print([b.title for b in queue]) - - -# Example 17 -from heapq import heappop - -def next_overdue_book(queue, now): - if queue: - book = queue[0] # Most overdue first - if book.due_date < now: - heappop(queue) # Remove the overdue book - return book - - raise NoOverdueBooks - - -# Example 18 -now = '2019-06-02' - -book = next_overdue_book(queue, now) -print(book.title) - -book = next_overdue_book(queue, now) -print(book.title) - -try: - next_overdue_book(queue, now) -except NoOverdueBooks: - pass # Expected -else: - assert False # Doesn't happen - - -# Example 19 -def heap_overdue_benchmark(count): - def prepare(): - to_add = list(range(count)) - random.shuffle(to_add) - return [], to_add - - def run(queue, to_add): - for i in to_add: - heappush(queue, i) - while queue: - heappop(queue) - - tests = timeit.repeat( - setup='queue, to_add = prepare()', - stmt=f'run(queue, to_add)', - globals=locals(), - repeat=100, - number=1) - - return print_results(count, tests) - - -# Example 20 -baseline = heap_overdue_benchmark(500) -for count in (1_000, 1_500, 2_000): - print() - comparison = heap_overdue_benchmark(count) - print_delta(baseline, comparison) - - -# Example 21 -@functools.total_ordering -class Book: - def __init__(self, title, due_date): - self.title = title - self.due_date = due_date - self.returned = False # New field - - def __lt__(self, other): - return self.due_date < other.due_date - - -# Example 22 -def next_overdue_book(queue, now): - while queue: - book = queue[0] - if book.returned: - heappop(queue) - continue - - if book.due_date < now: - heappop(queue) - return book - - break - - raise NoOverdueBooks - -queue = [] - -book = Book('Pride and Prejudice', '2019-06-01') -add_book(queue, book) - -book = Book('The Time Machine', '2019-05-30') -add_book(queue, book) -book.returned = True - -book = Book('Crime and Punishment', '2019-06-06') -add_book(queue, book) -book.returned = True - -book = Book('Wuthering Heights', '2019-06-12') -add_book(queue, book) - -now = '2019-06-11' - -book = next_overdue_book(queue, now) -assert book.title == 'Pride and Prejudice' - -try: - next_overdue_book(queue, now) -except NoOverdueBooks: - pass # Expected -else: - assert False # Doesn't happen - - -# Example 23 -def return_book(queue, book): - book.returned = True - -assert not book.returned -return_book(queue, book) -assert book.returned diff --git a/example_code/item_74.py b/example_code/item_74.py deleted file mode 100755 index dfdbdc9..0000000 --- a/example_code/item_74.py +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -def timecode_to_index(video_id, timecode): - return 1234 - # Returns the byte offset in the video data - -def request_chunk(video_id, byte_offset, size): - pass - # Returns size bytes of video_id's data from the offset - -video_id = ... -timecode = '01:09:14:28' -byte_offset = timecode_to_index(video_id, timecode) -size = 20 * 1024 * 1024 -video_data = request_chunk(video_id, byte_offset, size) - - -# Example 2 -class NullSocket: - def __init__(self): - self.handle = open(os.devnull, 'wb') - - def send(self, data): - self.handle.write(data) - -socket = ... # socket connection to client -video_data = ... # bytes containing data for video_id -byte_offset = ... # Requested starting position -size = 20 * 1024 * 1024 # Requested chunk size -import os - -socket = NullSocket() -video_data = 100 * os.urandom(1024 * 1024) -byte_offset = 1234 - -chunk = video_data[byte_offset:byte_offset + size] -socket.send(chunk) - - -# Example 3 -import timeit - -def run_test(): - chunk = video_data[byte_offset:byte_offset + size] - # Call socket.send(chunk), but ignoring for benchmark - -result = timeit.timeit( - stmt='run_test()', - globals=globals(), - number=100) / 100 - -print(f'{result:0.9f} seconds') - - -# Example 4 -data = b'shave and a haircut, two bits' -view = memoryview(data) -chunk = view[12:19] -print(chunk) -print('Size: ', chunk.nbytes) -print('Data in view: ', chunk.tobytes()) -print('Underlying data:', chunk.obj) - - -# Example 5 -video_view = memoryview(video_data) - -def run_test(): - chunk = video_view[byte_offset:byte_offset + size] - # Call socket.send(chunk), but ignoring for benchmark - -result = timeit.timeit( - stmt='run_test()', - globals=globals(), - number=100) / 100 - -print(f'{result:0.9f} seconds') - - -# Example 6 -class FakeSocket: - - def recv(self, size): - return video_view[byte_offset:byte_offset+size] - - def recv_into(self, buffer): - source_data = video_view[byte_offset:byte_offset+size] - buffer[:] = source_data - -socket = ... # socket connection to the client -video_cache = ... # Cache of incoming video stream -byte_offset = ... # Incoming buffer position -size = 1024 * 1024 # Incoming chunk size -socket = FakeSocket() -video_cache = video_data[:] -byte_offset = 1234 - -chunk = socket.recv(size) -video_view = memoryview(video_cache) -before = video_view[:byte_offset] -after = video_view[byte_offset + size:] -new_cache = b''.join([before, chunk, after]) - - -# Example 7 -def run_test(): - chunk = socket.recv(size) - before = video_view[:byte_offset] - after = video_view[byte_offset + size:] - new_cache = b''.join([before, chunk, after]) - -result = timeit.timeit( - stmt='run_test()', - globals=globals(), - number=100) / 100 - -print(f'{result:0.9f} seconds') - - -# Example 8 -try: - my_bytes = b'hello' - my_bytes[0] = b'\x79' -except: - logging.exception('Expected') -else: - assert False - - -# Example 9 -my_array = bytearray(b'hello') -my_array[0] = 0x79 -print(my_array) - - -# Example 10 -my_array = bytearray(b'row, row, row your boat') -my_view = memoryview(my_array) -write_view = my_view[3:13] -write_view[:] = b'-10 bytes-' -print(my_array) - - -# Example 11 -video_array = bytearray(video_cache) -write_view = memoryview(video_array) -chunk = write_view[byte_offset:byte_offset + size] -socket.recv_into(chunk) - - -# Example 12 -def run_test(): - chunk = write_view[byte_offset:byte_offset + size] - socket.recv_into(chunk) - -result = timeit.timeit( - stmt='run_test()', - globals=globals(), - number=100) / 100 - -print(f'{result:0.9f} seconds') diff --git a/example_code/item_75.py b/example_code/item_75.py deleted file mode 100755 index 5e43409..0000000 --- a/example_code/item_75.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -print('foo bar') - - -# Example 2 -my_value = 'foo bar' -print(str(my_value)) -print('%s' % my_value) -print(f'{my_value}') -print(format(my_value)) -print(my_value.__format__('s')) -print(my_value.__str__()) - - -# Example 3 -print(5) -print('5') - -int_value = 5 -str_value = '5' -print(f'{int_value} == {str_value} ?') - - -# Example 4 -a = '\x07' -print(repr(a)) - - -# Example 5 -b = eval(repr(a)) -assert a == b - - -# Example 6 -print(repr(5)) -print(repr('5')) - - -# Example 7 -print('%r' % 5) -print('%r' % '5') - -int_value = 5 -str_value = '5' -print(f'{int_value!r} != {str_value!r}') - - -# Example 8 -class OpaqueClass: - def __init__(self, x, y): - self.x = x - self.y = y - -obj = OpaqueClass(1, 'foo') -print(obj) - - -# Example 9 -class BetterClass: - def __init__(self, x, y): - self.x = x - self.y = y - - def __repr__(self): - return f'BetterClass({self.x!r}, {self.y!r})' - - -# Example 10 -obj = BetterClass(2, 'bar') -print(obj) - - -# Example 11 -obj = OpaqueClass(4, 'baz') -print(obj.__dict__) diff --git a/example_code/item_76/testing/assert_test.py b/example_code/item_76/testing/assert_test.py deleted file mode 100755 index ebf9bb5..0000000 --- a/example_code/item_76/testing/assert_test.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from unittest import TestCase, main -from utils import to_str - -class AssertTestCase(TestCase): - def test_assert_helper(self): - expected = 12 - found = 2 * 5 - self.assertEqual(expected, found) - - def test_assert_statement(self): - expected = 12 - found = 2 * 5 - assert expected == found - -if __name__ == '__main__': - main() diff --git a/example_code/item_76/testing/data_driven_test.py b/example_code/item_76/testing/data_driven_test.py deleted file mode 100755 index 8255196..0000000 --- a/example_code/item_76/testing/data_driven_test.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from unittest import TestCase, main -from utils import to_str - -class DataDrivenTestCase(TestCase): - def test_good(self): - good_cases = [ - (b'my bytes', 'my bytes'), - ('no error', b'no error'), # This one will fail - ('other str', 'other str'), - ] - for value, expected in good_cases: - with self.subTest(value): - self.assertEqual(expected, to_str(value)) - - def test_bad(self): - bad_cases = [ - (object(), TypeError), - (b'\xfa\xfa', UnicodeDecodeError), - ] - for value, exception in bad_cases: - with self.subTest(value): - with self.assertRaises(exception): - to_str(value) - -if __name__ == '__main__': - main() diff --git a/example_code/item_76/testing/helper_test.py b/example_code/item_76/testing/helper_test.py deleted file mode 100755 index 6796b2a..0000000 --- a/example_code/item_76/testing/helper_test.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from unittest import TestCase, main - -def sum_squares(values): - cumulative = 0 - for value in values: - cumulative += value ** 2 - yield cumulative - -class HelperTestCase(TestCase): - def verify_complex_case(self, values, expected): - expect_it = iter(expected) - found_it = iter(sum_squares(values)) - test_it = zip(expect_it, found_it) - - for i, (expect, found) in enumerate(test_it): - self.assertEqual( - expect, - found, - f'Index {i} is wrong') - - # Verify both generators are exhausted - try: - next(expect_it) - except StopIteration: - pass - else: - self.fail('Expected longer than found') - - try: - next(found_it) - except StopIteration: - pass - else: - self.fail('Found longer than expected') - - def test_wrong_lengths(self): - values = [1.1, 2.2, 3.3] - expected = [ - 1.1**2, - ] - self.verify_complex_case(values, expected) - - def test_wrong_results(self): - values = [1.1, 2.2, 3.3] - expected = [ - 1.1**2, - 1.1**2 + 2.2**2, - 1.1**2 + 2.2**2 + 3.3**2 + 4.4**2, - ] - self.verify_complex_case(values, expected) - -if __name__ == '__main__': - main() diff --git a/example_code/item_76/testing/utils.py b/example_code/item_76/testing/utils.py deleted file mode 100755 index 30b0787..0000000 --- a/example_code/item_76/testing/utils.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -def to_str(data): - if isinstance(data, str): - return data - elif isinstance(data, bytes): - return data.decode('utf-8') - else: - raise TypeError('Must supply str or bytes, ' - 'found: %r' % data) diff --git a/example_code/item_76/testing/utils_error_test.py b/example_code/item_76/testing/utils_error_test.py deleted file mode 100755 index 36ccbbe..0000000 --- a/example_code/item_76/testing/utils_error_test.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from unittest import TestCase, main -from utils import to_str - -class UtilsErrorTestCase(TestCase): - def test_to_str_bad(self): - with self.assertRaises(TypeError): - to_str(object()) - - def test_to_str_bad_encoding(self): - with self.assertRaises(UnicodeDecodeError): - to_str(b'\xfa\xfa') - -if __name__ == '__main__': - main() diff --git a/example_code/item_76/testing/utils_test.py b/example_code/item_76/testing/utils_test.py deleted file mode 100755 index 258dcd6..0000000 --- a/example_code/item_76/testing/utils_test.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from unittest import TestCase, main -from utils import to_str - -class UtilsTestCase(TestCase): - def test_to_str_bytes(self): - self.assertEqual('hello', to_str(b'hello')) - - def test_to_str_str(self): - self.assertEqual('hello', to_str('hello')) - - def test_failing(self): - self.assertEqual('incorrect', to_str('hello')) - -if __name__ == '__main__': - main() diff --git a/example_code/item_77/testing/environment_test.py b/example_code/item_77/testing/environment_test.py deleted file mode 100755 index 527f958..0000000 --- a/example_code/item_77/testing/environment_test.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from pathlib import Path -from tempfile import TemporaryDirectory -from unittest import TestCase, main - -class EnvironmentTest(TestCase): - def setUp(self): - self.test_dir = TemporaryDirectory() - self.test_path = Path(self.test_dir.name) - - def tearDown(self): - self.test_dir.cleanup() - - def test_modify_file(self): - with open(self.test_path / 'data.bin', 'w') as f: - f.write('hello') - -if __name__ == '__main__': - main() diff --git a/example_code/item_77/testing/integration_test.py b/example_code/item_77/testing/integration_test.py deleted file mode 100755 index c722c87..0000000 --- a/example_code/item_77/testing/integration_test.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from unittest import TestCase, main - -def setUpModule(): - print('* Module setup') - -def tearDownModule(): - print('* Module clean-up') - -class IntegrationTest(TestCase): - def setUp(self): - print('* Test setup') - - def tearDown(self): - print('* Test clean-up') - - def test_end_to_end1(self): - print('* Test 1') - - def test_end_to_end2(self): - print('* Test 2') - -if __name__ == '__main__': - main() diff --git a/example_code/item_78.py b/example_code/item_78.py deleted file mode 100755 index 49f9c0f..0000000 --- a/example_code/item_78.py +++ /dev/null @@ -1,307 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class DatabaseConnection: - def __init__(self, host, port): - pass - -class DatabaseConnectionError(Exception): - pass - -def get_animals(database, species): - # Query the Database - raise DatabaseConnectionError('Not connected') - # Return a list of (name, last_mealtime) tuples - - -# Example 2 -try: - database = DatabaseConnection('localhost', '4444') - - get_animals(database, 'Meerkat') -except: - logging.exception('Expected') -else: - assert False - - -# Example 3 -from datetime import datetime -from unittest.mock import Mock - -mock = Mock(spec=get_animals) -expected = [ - ('Spot', datetime(2019, 6, 5, 11, 15)), - ('Fluffy', datetime(2019, 6, 5, 12, 30)), - ('Jojo', datetime(2019, 6, 5, 12, 45)), -] -mock.return_value = expected - - -# Example 4 -try: - mock.does_not_exist -except: - logging.exception('Expected') -else: - assert False - - -# Example 5 -database = object() -result = mock(database, 'Meerkat') -assert result == expected - - -# Example 6 -mock.assert_called_once_with(database, 'Meerkat') - - -# Example 7 -try: - mock.assert_called_once_with(database, 'Giraffe') -except: - logging.exception('Expected') -else: - assert False - - -# Example 8 -from unittest.mock import ANY - -mock = Mock(spec=get_animals) -mock('database 1', 'Rabbit') -mock('database 2', 'Bison') -mock('database 3', 'Meerkat') - -mock.assert_called_with(ANY, 'Meerkat') - - -# Example 9 -try: - class MyError(Exception): - pass - - mock = Mock(spec=get_animals) - mock.side_effect = MyError('Whoops! Big problem') - result = mock(database, 'Meerkat') -except: - logging.exception('Expected') -else: - assert False - - -# Example 10 -def get_food_period(database, species): - # Query the Database - pass - # Return a time delta - -def feed_animal(database, name, when): - # Write to the Database - pass - -def do_rounds(database, species): - now = datetime.datetime.utcnow() - feeding_timedelta = get_food_period(database, species) - animals = get_animals(database, species) - fed = 0 - - for name, last_mealtime in animals: - if (now - last_mealtime) > feeding_timedelta: - feed_animal(database, name, now) - fed += 1 - - return fed - - -# Example 11 -def do_rounds(database, species, *, - now_func=datetime.utcnow, - food_func=get_food_period, - animals_func=get_animals, - feed_func=feed_animal): - now = now_func() - feeding_timedelta = food_func(database, species) - animals = animals_func(database, species) - fed = 0 - - for name, last_mealtime in animals: - if (now - last_mealtime) > feeding_timedelta: - feed_func(database, name, now) - fed += 1 - - return fed - - -# Example 12 -from datetime import timedelta - -now_func = Mock(spec=datetime.utcnow) -now_func.return_value = datetime(2019, 6, 5, 15, 45) - -food_func = Mock(spec=get_food_period) -food_func.return_value = timedelta(hours=3) - -animals_func = Mock(spec=get_animals) -animals_func.return_value = [ - ('Spot', datetime(2019, 6, 5, 11, 15)), - ('Fluffy', datetime(2019, 6, 5, 12, 30)), - ('Jojo', datetime(2019, 6, 5, 12, 45)), -] - -feed_func = Mock(spec=feed_animal) - - -# Example 13 -result = do_rounds( - database, - 'Meerkat', - now_func=now_func, - food_func=food_func, - animals_func=animals_func, - feed_func=feed_func) - -assert result == 2 - - -# Example 14 -from unittest.mock import call - -food_func.assert_called_once_with(database, 'Meerkat') - -animals_func.assert_called_once_with(database, 'Meerkat') - -feed_func.assert_has_calls( - [ - call(database, 'Spot', now_func.return_value), - call(database, 'Fluffy', now_func.return_value), - ], - any_order=True) - - -# Example 15 -from unittest.mock import patch - -print('Outside patch:', get_animals) - -with patch('__main__.get_animals'): - print('Inside patch: ', get_animals) - -print('Outside again:', get_animals) - - -# Example 16 -try: - fake_now = datetime(2019, 6, 5, 15, 45) - - with patch('datetime.datetime.utcnow'): - datetime.utcnow.return_value = fake_now -except: - logging.exception('Expected') -else: - assert False - - -# Example 17 -def get_do_rounds_time(): - return datetime.datetime.utcnow() - -def do_rounds(database, species): - now = get_do_rounds_time() - -with patch('__main__.get_do_rounds_time'): - pass - - -# Example 18 -def do_rounds(database, species, *, utcnow=datetime.utcnow): - now = utcnow() - feeding_timedelta = get_food_period(database, species) - animals = get_animals(database, species) - fed = 0 - - for name, last_mealtime in animals: - if (now - last_mealtime) > feeding_timedelta: - feed_animal(database, name, now) - fed += 1 - - return fed - - -# Example 19 -from unittest.mock import DEFAULT - -with patch.multiple('__main__', - autospec=True, - get_food_period=DEFAULT, - get_animals=DEFAULT, - feed_animal=DEFAULT): - now_func = Mock(spec=datetime.utcnow) - now_func.return_value = datetime(2019, 6, 5, 15, 45) - get_food_period.return_value = timedelta(hours=3) - get_animals.return_value = [ - ('Spot', datetime(2019, 6, 5, 11, 15)), - ('Fluffy', datetime(2019, 6, 5, 12, 30)), - ('Jojo', datetime(2019, 6, 5, 12, 45)) - ] - - -# Example 20 - result = do_rounds(database, 'Meerkat', utcnow=now_func) - assert result == 2 - - get_food_period.assert_called_once_with(database, 'Meerkat') - get_animals.assert_called_once_with(database, 'Meerkat') - feed_animal.assert_has_calls( - [ - call(database, 'Spot', now_func.return_value), - call(database, 'Fluffy', now_func.return_value), - ], - any_order=True) diff --git a/example_code/item_79.py b/example_code/item_79.py deleted file mode 100755 index 1769e1a..0000000 --- a/example_code/item_79.py +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -class ZooDatabase: - - def get_animals(self, species): - pass - - def get_food_period(self, species): - pass - - def feed_animal(self, name, when): - pass - - -# Example 2 -from datetime import datetime - -def do_rounds(database, species, *, utcnow=datetime.utcnow): - now = utcnow() - feeding_timedelta = database.get_food_period(species) - animals = database.get_animals(species) - fed = 0 - - for name, last_mealtime in animals: - if (now - last_mealtime) >= feeding_timedelta: - database.feed_animal(name, now) - fed += 1 - - return fed - - -# Example 3 -from unittest.mock import Mock - -database = Mock(spec=ZooDatabase) -print(database.feed_animal) -database.feed_animal() -database.feed_animal.assert_any_call() - - -# Example 4 -from datetime import timedelta -from unittest.mock import call - -now_func = Mock(spec=datetime.utcnow) -now_func.return_value = datetime(2019, 6, 5, 15, 45) - -database = Mock(spec=ZooDatabase) -database.get_food_period.return_value = timedelta(hours=3) -database.get_animals.return_value = [ - ('Spot', datetime(2019, 6, 5, 11, 15)), - ('Fluffy', datetime(2019, 6, 5, 12, 30)), - ('Jojo', datetime(2019, 6, 5, 12, 55)) -] - - -# Example 5 -result = do_rounds(database, 'Meerkat', utcnow=now_func) -assert result == 2 - -database.get_food_period.assert_called_once_with('Meerkat') -database.get_animals.assert_called_once_with('Meerkat') -database.feed_animal.assert_has_calls( - [ - call('Spot', now_func.return_value), - call('Fluffy', now_func.return_value), - ], - any_order=True) - - -# Example 6 -try: - database.bad_method_name() -except: - logging.exception('Expected') -else: - assert False - - -# Example 7 -DATABASE = None - -def get_database(): - global DATABASE - if DATABASE is None: - DATABASE = ZooDatabase() - return DATABASE - -def main(argv): - database = get_database() - species = argv[1] - count = do_rounds(database, species) - print(f'Fed {count} {species}(s)') - return 0 - - -# Example 8 -import contextlib -import io -from unittest.mock import patch - -with patch('__main__.DATABASE', spec=ZooDatabase): - now = datetime.utcnow() - - DATABASE.get_food_period.return_value = timedelta(hours=3) - DATABASE.get_animals.return_value = [ - ('Spot', now - timedelta(minutes=4.5)), - ('Fluffy', now - timedelta(hours=3.25)), - ('Jojo', now - timedelta(hours=3)), - ] - - fake_stdout = io.StringIO() - with contextlib.redirect_stdout(fake_stdout): - main(['program name', 'Meerkat']) - - found = fake_stdout.getvalue() - expected = 'Fed 2 Meerkat(s)\n' - - assert found == expected diff --git a/example_code/item_80/debugging/always_breakpoint.py b/example_code/item_80/debugging/always_breakpoint.py deleted file mode 100755 index 582a050..0000000 --- a/example_code/item_80/debugging/always_breakpoint.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import math - -def compute_rmse(observed, ideal): - total_err_2 = 0 - count = 0 - for got, wanted in zip(observed, ideal): - err_2 = (got - wanted) ** 2 - breakpoint() # Start the debugger here - total_err_2 += err_2 - count += 1 - - mean_err = total_err_2 / count - rmse = math.sqrt(mean_err) - return rmse - -result = compute_rmse( - [1.8, 1.7, 3.2, 6], - [2, 1.5, 3, 5]) -print(result) diff --git a/example_code/item_80/debugging/conditional_breakpoint.py b/example_code/item_80/debugging/conditional_breakpoint.py deleted file mode 100755 index 6540c3f..0000000 --- a/example_code/item_80/debugging/conditional_breakpoint.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import math -def compute_rmse(observed, ideal): - total_err_2 = 0 - count = 0 - for got, wanted in zip(observed, ideal): - err_2 = (got - wanted) ** 2 - if err_2 >= 1: # Start the debugger if True - breakpoint() - total_err_2 += err_2 - count += 1 - mean_err = total_err_2 / count - rmse = math.sqrt(mean_err) - return rmse - -result = compute_rmse( - [1.8, 1.7, 3.2, 7], - [2, 1.5, 3, 5]) -print(result) diff --git a/example_code/item_80/debugging/my_module.py b/example_code/item_80/debugging/my_module.py deleted file mode 100755 index e22ca05..0000000 --- a/example_code/item_80/debugging/my_module.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import math - -def squared_error(point, mean): - err = point - mean - return err ** 2 - -def compute_variance(data): - mean = sum(data) / len(data) - err_2_sum = sum(squared_error(x, mean) for x in data) - variance = err_2_sum / (len(data) - 1) - return variance - -def compute_stddev(data): - variance = compute_variance(data) - return math.sqrt(variance) diff --git a/example_code/item_80/debugging/postmortem_breakpoint.py b/example_code/item_80/debugging/postmortem_breakpoint.py deleted file mode 100755 index 9a6e365..0000000 --- a/example_code/item_80/debugging/postmortem_breakpoint.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import math - -def compute_rmse(observed, ideal): - total_err_2 = 0 - count = 0 - for got, wanted in zip(observed, ideal): - err_2 = (got - wanted) ** 2 - total_err_2 += err_2 - count += 1 - - mean_err = total_err_2 / count - rmse = math.sqrt(mean_err) - return rmse - -result = compute_rmse( - [1.8, 1.7, 3.2, 7j], # Bad input - [2, 1.5, 3, 5]) -print(result) diff --git a/example_code/item_81/tracemalloc/top_n.py b/example_code/item_81/tracemalloc/top_n.py deleted file mode 100755 index 8e7e698..0000000 --- a/example_code/item_81/tracemalloc/top_n.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import tracemalloc - -tracemalloc.start(10) # Set stack depth -time1 = tracemalloc.take_snapshot() # Before snapshot - -import waste_memory - -x = waste_memory.run() # Usage to debug -time2 = tracemalloc.take_snapshot() # After snapshot - -stats = time2.compare_to(time1, 'lineno') # Compare snapshots -for stat in stats[:3]: - print(stat) diff --git a/example_code/item_81/tracemalloc/using_gc.py b/example_code/item_81/tracemalloc/using_gc.py deleted file mode 100755 index 1fe0574..0000000 --- a/example_code/item_81/tracemalloc/using_gc.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import gc - -found_objects = gc.get_objects() -print('Before:', len(found_objects)) - -import waste_memory - -hold_reference = waste_memory.run() - -found_objects = gc.get_objects() -print('After: ', len(found_objects)) -for obj in found_objects[:3]: - print(repr(obj)[:100]) - -print('...') diff --git a/example_code/item_81/tracemalloc/waste_memory.py b/example_code/item_81/tracemalloc/waste_memory.py deleted file mode 100755 index 5afd71b..0000000 --- a/example_code/item_81/tracemalloc/waste_memory.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# waste_memory.py -import os - -class MyObject: - def __init__(self): - self.data = os.urandom(100) - -def get_data(): - values = [] - for _ in range(100): - obj = MyObject() - values.append(obj) - return values - -def run(): - deep_values = [] - for _ in range(100): - deep_values.append(get_data()) - return deep_values diff --git a/example_code/item_81/tracemalloc/with_trace.py b/example_code/item_81/tracemalloc/with_trace.py deleted file mode 100755 index 4f506ee..0000000 --- a/example_code/item_81/tracemalloc/with_trace.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import tracemalloc - -tracemalloc.start(10) -time1 = tracemalloc.take_snapshot() - -import waste_memory - -x = waste_memory.run() -time2 = tracemalloc.take_snapshot() - -stats = time2.compare_to(time1, 'traceback') -top = stats[0] -print('Biggest offender is:') -print('\n'.join(top.traceback.format())) diff --git a/example_code/item_84.py b/example_code/item_84.py deleted file mode 100755 index f673237..0000000 --- a/example_code/item_84.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -def palindrome(word): - """Return True if the given word is a palindrome.""" - return word == word[::-1] - -assert palindrome('tacocat') -assert not palindrome('banana') - - -# Example 2 -print(repr(palindrome.__doc__)) - - -# Example 3 -"""Library for finding linguistic patterns in words. - -Testing how words relate to each other can be tricky sometimes! -This module provides easy ways to determine when words you've -found have special properties. - -Available functions: -- palindrome: Determine if a word is a palindrome. -- check_anagram: Determine if two words are anagrams. -... -""" - - -# Example 4 -class Player: - """Represents a player of the game. - - Subclasses may override the 'tick' method to provide - custom animations for the player's movement depending - on their power level, etc. - - Public attributes: - - power: Unused power-ups (float between 0 and 1). - - coins: Coins found during the level (integer). - """ - - -# Example 5 -import itertools -def find_anagrams(word, dictionary): - """Find all anagrams for a word. - - This function only runs as fast as the test for - membership in the 'dictionary' container. - - Args: - word: String of the target word. - dictionary: collections.abc.Container with all - strings that are known to be actual words. - - Returns: - List of anagrams that were found. Empty if - none were found. - """ - permutations = itertools.permutations(word, len(word)) - possible = (''.join(x) for x in permutations) - found = {word for word in possible if word in dictionary} - return list(found) - -assert find_anagrams('pancakes', ['scanpeak']) == ['scanpeak'] diff --git a/example_code/item_84_example_06.py b/example_code/item_84_example_06.py deleted file mode 100755 index 99a4915..0000000 --- a/example_code/item_84_example_06.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -# Example 6 -# Check types in this file with: python -m mypy - -from typing import Container, List - -def find_anagrams(word: str, - dictionary: Container[str]) -> List[str]: - pass diff --git a/example_code/item_84_example_07.py b/example_code/item_84_example_07.py deleted file mode 100755 index e6d4bc2..0000000 --- a/example_code/item_84_example_07.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -# Example 7 -# Check types in this file with: python -m mypy - -from typing import Container, List - -def find_anagrams(word: str, - dictionary: Container[str]) -> List[str]: - """Find all anagrams for a word. - - This function only runs as fast as the test for - membership in the 'dictionary' container. - - Args: - word: Target word. - dictionary: All known actual words. - - Returns: - Anagrams that were found. - """ - pass diff --git a/example_code/item_85/api_package/api_consumer.py b/example_code/item_85/api_package/api_consumer.py deleted file mode 100755 index 46092f7..0000000 --- a/example_code/item_85/api_package/api_consumer.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from mypackage import * - -a = Projectile(1.5, 3) -b = Projectile(4, 1.7) -after_a, after_b = simulate_collision(a, b) -print(after_a.__dict__, after_b.__dict__) - -import mypackage -try: - mypackage._dot_product - assert False -except AttributeError: - pass # Expected - -mypackage.utils._dot_product # But this is defined diff --git a/example_code/item_85/api_package/main.py b/example_code/item_85/api_package/main.py deleted file mode 100755 index 636228d..0000000 --- a/example_code/item_85/api_package/main.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from mypackage import utils diff --git a/example_code/item_85/api_package/mypackage/__init__.py b/example_code/item_85/api_package/mypackage/__init__.py deleted file mode 100755 index 025222d..0000000 --- a/example_code/item_85/api_package/mypackage/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -__all__ = [] -from . models import * -__all__ += models.__all__ -from . utils import * -__all__ += utils.__all__ diff --git a/example_code/item_85/api_package/mypackage/models.py b/example_code/item_85/api_package/mypackage/models.py deleted file mode 100755 index b72c3c6..0000000 --- a/example_code/item_85/api_package/mypackage/models.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -__all__ = ['Projectile'] - -class Projectile: - def __init__(self, mass, velocity): - self.mass = mass - self.velocity = velocity diff --git a/example_code/item_85/api_package/mypackage/utils.py b/example_code/item_85/api_package/mypackage/utils.py deleted file mode 100755 index fc37afa..0000000 --- a/example_code/item_85/api_package/mypackage/utils.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from . models import Projectile - -__all__ = ['simulate_collision'] - -def _dot_product(a, b): - pass - -def simulate_collision(a, b): - after_a = Projectile(-a.mass, -a.velocity) - after_b = Projectile(-b.mass, -b.velocity) - return after_a, after_b diff --git a/example_code/item_85/namespace_package/analysis/__init__.py b/example_code/item_85/namespace_package/analysis/__init__.py deleted file mode 100755 index 18e252f..0000000 --- a/example_code/item_85/namespace_package/analysis/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - diff --git a/example_code/item_85/namespace_package/analysis/utils.py b/example_code/item_85/namespace_package/analysis/utils.py deleted file mode 100755 index 972a3fb..0000000 --- a/example_code/item_85/namespace_package/analysis/utils.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import math -def log_base2_bucket(value): - return math.log(value, 2) - -def inspect(value): - pass diff --git a/example_code/item_85/namespace_package/frontend/__init__.py b/example_code/item_85/namespace_package/frontend/__init__.py deleted file mode 100755 index 18e252f..0000000 --- a/example_code/item_85/namespace_package/frontend/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - diff --git a/example_code/item_85/namespace_package/frontend/utils.py b/example_code/item_85/namespace_package/frontend/utils.py deleted file mode 100755 index 69ed8a7..0000000 --- a/example_code/item_85/namespace_package/frontend/utils.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -def stringify(value): - return str(value) - -def inspect(value): - pass diff --git a/example_code/item_85/namespace_package/main.py b/example_code/item_85/namespace_package/main.py deleted file mode 100755 index 4bbf563..0000000 --- a/example_code/item_85/namespace_package/main.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from analysis.utils import log_base2_bucket -from frontend.utils import stringify - -bucket = stringify(log_base2_bucket(33)) -print(repr(bucket)) diff --git a/example_code/item_85/namespace_package/main2.py b/example_code/item_85/namespace_package/main2.py deleted file mode 100755 index 8b4bb85..0000000 --- a/example_code/item_85/namespace_package/main2.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from analysis.utils import inspect -from frontend.utils import inspect # Overwrites! -'frontend' in inspect.__module__ -print(inspect.__module__) diff --git a/example_code/item_85/namespace_package/main3.py b/example_code/item_85/namespace_package/main3.py deleted file mode 100755 index e013b73..0000000 --- a/example_code/item_85/namespace_package/main3.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from analysis.utils import inspect as analysis_inspect -from frontend.utils import inspect as frontend_inspect - -value = 33 -if analysis_inspect(value) == frontend_inspect(value): - print('Inspection equal!') diff --git a/example_code/item_85/namespace_package/main4.py b/example_code/item_85/namespace_package/main4.py deleted file mode 100755 index 0dfb064..0000000 --- a/example_code/item_85/namespace_package/main4.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import analysis.utils -import frontend.utils - -value = 33 -if (analysis.utils.inspect(value) == - frontend.utils.inspect(value)): - print('Inspection equal!') diff --git a/example_code/item_86.py b/example_code/item_86.py deleted file mode 100755 index 492fa14..0000000 --- a/example_code/item_86.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 4 -# db_connection.py -import sys - -class Win32Database: - pass - -class PosixDatabase: - pass - -if sys.platform.startswith('win32'): - Database = Win32Database -else: - Database = PosixDatabase diff --git a/example_code/item_86/module_scope/db_connection.py b/example_code/item_86/module_scope/db_connection.py deleted file mode 100755 index df269db..0000000 --- a/example_code/item_86/module_scope/db_connection.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# db_connection.py -import __main__ - -class TestingDatabase: - pass - -class RealDatabase: - pass - -if __main__.TESTING: - Database = TestingDatabase -else: - Database = RealDatabase diff --git a/example_code/item_86/module_scope/dev_main.py b/example_code/item_86/module_scope/dev_main.py deleted file mode 100755 index cfcb4c5..0000000 --- a/example_code/item_86/module_scope/dev_main.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -TESTING = True - -import db_connection - -db = db_connection.Database() diff --git a/example_code/item_86/module_scope/prod_main.py b/example_code/item_86/module_scope/prod_main.py deleted file mode 100755 index 3dda7da..0000000 --- a/example_code/item_86/module_scope/prod_main.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -TESTING = False - -import db_connection - -db = db_connection.Database() diff --git a/example_code/item_87.py b/example_code/item_87.py deleted file mode 100755 index 07c20c9..0000000 --- a/example_code/item_87.py +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -# my_module.py -def determine_weight(volume, density): - if density <= 0: - raise ValueError('Density must be positive') - -try: - determine_weight(1, 0) -except ValueError: - pass -else: - assert False - - -# Example 2 -# my_module.py -class Error(Exception): - """Base-class for all exceptions raised by this module.""" - -class InvalidDensityError(Error): - """There was a problem with a provided density value.""" - -class InvalidVolumeError(Error): - """There was a problem with the provided weight value.""" - -def determine_weight(volume, density): - if density < 0: - raise InvalidDensityError('Density must be positive') - if volume < 0: - raise InvalidVolumeError('Volume must be positive') - if volume == 0: - density / volume - - -# Example 3 -class my_module: - Error = Error - InvalidDensityError = InvalidDensityError - - @staticmethod - def determine_weight(volume, density): - if density < 0: - raise InvalidDensityError('Density must be positive') - if volume < 0: - raise InvalidVolumeError('Volume must be positive') - if volume == 0: - density / volume - -try: - weight = my_module.determine_weight(1, -1) -except my_module.Error: - logging.exception('Unexpected error') -else: - assert False - - -# Example 4 -SENTINEL = object() -weight = SENTINEL -try: - weight = my_module.determine_weight(-1, 1) -except my_module.InvalidDensityError: - weight = 0 -except my_module.Error: - logging.exception('Bug in the calling code') -else: - assert False - -assert weight is SENTINEL - - -# Example 5 -try: - weight = SENTINEL - try: - weight = my_module.determine_weight(0, 1) - except my_module.InvalidDensityError: - weight = 0 - except my_module.Error: - logging.exception('Bug in the calling code') - except Exception: - logging.exception('Bug in the API code!') - raise # Re-raise exception to the caller - else: - assert False - - assert weight == 0 -except: - logging.exception('Expected') -else: - assert False - - -# Example 6 -# my_module.py - -class NegativeDensityError(InvalidDensityError): - """A provided density value was negative.""" - - -def determine_weight(volume, density): - if density < 0: - raise NegativeDensityError('Density must be positive') - - -# Example 7 -try: - my_module.NegativeDensityError = NegativeDensityError - my_module.determine_weight = determine_weight - try: - weight = my_module.determine_weight(1, -1) - except my_module.NegativeDensityError: - raise ValueError('Must supply non-negative density') - except my_module.InvalidDensityError: - weight = 0 - except my_module.Error: - logging.exception('Bug in the calling code') - except Exception: - logging.exception('Bug in the API code!') - raise - else: - assert False -except: - logging.exception('Expected') -else: - assert False - - -# Example 8 -# my_module.py -class Error(Exception): - """Base-class for all exceptions raised by this module.""" - -class WeightError(Error): - """Base-class for weight calculation errors.""" - -class VolumeError(Error): - """Base-class for volume calculation errors.""" - -class DensityError(Error): - """Base-class for density calculation errors.""" diff --git a/example_code/item_88/recursive_import_bad/app.py b/example_code/item_88/recursive_import_bad/app.py deleted file mode 100755 index cac1ea8..0000000 --- a/example_code/item_88/recursive_import_bad/app.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dialog - -class Prefs: - def get(self, name): - pass - -prefs = Prefs() -dialog.show() diff --git a/example_code/item_88/recursive_import_bad/dialog.py b/example_code/item_88/recursive_import_bad/dialog.py deleted file mode 100755 index 44aa232..0000000 --- a/example_code/item_88/recursive_import_bad/dialog.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import app - -class Dialog: - def __init__(self, save_dir): - self.save_dir = save_dir - -save_dialog = Dialog(app.prefs.get('save_dir')) - -def show(): - print('Showing the dialog!') diff --git a/example_code/item_88/recursive_import_bad/main.py b/example_code/item_88/recursive_import_bad/main.py deleted file mode 100755 index eeeba96..0000000 --- a/example_code/item_88/recursive_import_bad/main.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import app diff --git a/example_code/item_88/recursive_import_dynamic/app.py b/example_code/item_88/recursive_import_dynamic/app.py deleted file mode 100755 index cac1ea8..0000000 --- a/example_code/item_88/recursive_import_dynamic/app.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dialog - -class Prefs: - def get(self, name): - pass - -prefs = Prefs() -dialog.show() diff --git a/example_code/item_88/recursive_import_dynamic/dialog.py b/example_code/item_88/recursive_import_dynamic/dialog.py deleted file mode 100755 index de500cf..0000000 --- a/example_code/item_88/recursive_import_dynamic/dialog.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reenabling this will break things. -# import app - -class Dialog: - def __init__(self): - pass - -# Using this instead will break things -# save_dialog = Dialog(app.prefs.get('save_dir')) -save_dialog = Dialog() - -def show(): - import app # Dynamic import - save_dialog.save_dir = app.prefs.get('save_dir') - print('Showing the dialog!') diff --git a/example_code/item_88/recursive_import_dynamic/main.py b/example_code/item_88/recursive_import_dynamic/main.py deleted file mode 100755 index eeeba96..0000000 --- a/example_code/item_88/recursive_import_dynamic/main.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import app diff --git a/example_code/item_88/recursive_import_nosideeffects/app.py b/example_code/item_88/recursive_import_nosideeffects/app.py deleted file mode 100755 index 7cd5bc0..0000000 --- a/example_code/item_88/recursive_import_nosideeffects/app.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dialog - -class Prefs: - def get(self, name): - pass - -prefs = Prefs() - -def configure(): - pass diff --git a/example_code/item_88/recursive_import_nosideeffects/dialog.py b/example_code/item_88/recursive_import_nosideeffects/dialog.py deleted file mode 100755 index 9c5b772..0000000 --- a/example_code/item_88/recursive_import_nosideeffects/dialog.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import app - -class Dialog: - def __init__(self): - pass - -save_dialog = Dialog() - -def show(): - print('Showing the dialog!') - -def configure(): - save_dialog.save_dir = app.prefs.get('save_dir') diff --git a/example_code/item_88/recursive_import_nosideeffects/main.py b/example_code/item_88/recursive_import_nosideeffects/main.py deleted file mode 100755 index 4bcbbff..0000000 --- a/example_code/item_88/recursive_import_nosideeffects/main.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import app -import dialog - -app.configure() -dialog.configure() - -dialog.show() diff --git a/example_code/item_88/recursive_import_ordering/app.py b/example_code/item_88/recursive_import_ordering/app.py deleted file mode 100755 index c5abf30..0000000 --- a/example_code/item_88/recursive_import_ordering/app.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -class Prefs: - def get(self, name): - pass - -prefs = Prefs() - -import dialog # Moved -dialog.show() diff --git a/example_code/item_88/recursive_import_ordering/dialog.py b/example_code/item_88/recursive_import_ordering/dialog.py deleted file mode 100755 index 44aa232..0000000 --- a/example_code/item_88/recursive_import_ordering/dialog.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import app - -class Dialog: - def __init__(self, save_dir): - self.save_dir = save_dir - -save_dialog = Dialog(app.prefs.get('save_dir')) - -def show(): - print('Showing the dialog!') diff --git a/example_code/item_88/recursive_import_ordering/main.py b/example_code/item_88/recursive_import_ordering/main.py deleted file mode 100755 index eeeba96..0000000 --- a/example_code/item_88/recursive_import_ordering/main.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import app diff --git a/example_code/item_89.py b/example_code/item_89.py deleted file mode 100755 index e9fe12a..0000000 --- a/example_code/item_89.py +++ /dev/null @@ -1,234 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -def print_distance(speed, duration): - distance = speed * duration - print(f'{distance} miles') - -print_distance(5, 2.5) - - -# Example 2 -print_distance(1000, 3) - - -# Example 3 -CONVERSIONS = { - 'mph': 1.60934 / 3600 * 1000, # m/s - 'hours': 3600, # seconds - 'miles': 1.60934 * 1000, # m - 'meters': 1, # m - 'm/s': 1, # m - 'seconds': 1, # s -} - -def convert(value, units): - rate = CONVERSIONS[units] - return rate * value - -def localize(value, units): - rate = CONVERSIONS[units] - return value / rate - -def print_distance(speed, duration, *, - speed_units='mph', - time_units='hours', - distance_units='miles'): - norm_speed = convert(speed, speed_units) - norm_duration = convert(duration, time_units) - norm_distance = norm_speed * norm_duration - distance = localize(norm_distance, distance_units) - print(f'{distance} {distance_units}') - - -# Example 4 -print_distance(1000, 3, - speed_units='meters', - time_units='seconds') - - -# Example 5 -import warnings - -def print_distance(speed, duration, *, - speed_units=None, - time_units=None, - distance_units=None): - if speed_units is None: - warnings.warn( - 'speed_units required', DeprecationWarning) - speed_units = 'mph' - - if time_units is None: - warnings.warn( - 'time_units required', DeprecationWarning) - time_units = 'hours' - - if distance_units is None: - warnings.warn( - 'distance_units required', DeprecationWarning) - distance_units = 'miles' - - norm_speed = convert(speed, speed_units) - norm_duration = convert(duration, time_units) - norm_distance = norm_speed * norm_duration - distance = localize(norm_distance, distance_units) - print(f'{distance} {distance_units}') - - -# Example 6 -import contextlib -import io - -fake_stderr = io.StringIO() -with contextlib.redirect_stderr(fake_stderr): - print_distance(1000, 3, - speed_units='meters', - time_units='seconds') - -print(fake_stderr.getvalue()) - - -# Example 7 -def require(name, value, default): - if value is not None: - return value - warnings.warn( - f'{name} will be required soon, update your code', - DeprecationWarning, - stacklevel=3) - return default - -def print_distance(speed, duration, *, - speed_units=None, - time_units=None, - distance_units=None): - speed_units = require('speed_units', speed_units, 'mph') - time_units = require('time_units', time_units, 'hours') - distance_units = require( - 'distance_units', distance_units, 'miles') - - norm_speed = convert(speed, speed_units) - norm_duration = convert(duration, time_units) - norm_distance = norm_speed * norm_duration - distance = localize(norm_distance, distance_units) - print(f'{distance} {distance_units}') - - -# Example 8 -import contextlib -import io - -fake_stderr = io.StringIO() -with contextlib.redirect_stderr(fake_stderr): - print_distance(1000, 3, - speed_units='meters', - time_units='seconds') - -print(fake_stderr.getvalue()) - - -# Example 9 -warnings.simplefilter('error') -try: - warnings.warn('This usage is deprecated', - DeprecationWarning) -except DeprecationWarning: - pass # Expected -else: - assert False - -warnings.resetwarnings() - - -# Example 10 -warnings.resetwarnings() - -warnings.simplefilter('ignore') -warnings.warn('This will not be printed to stderr') - -warnings.resetwarnings() - - -# Example 11 -import logging - -fake_stderr = io.StringIO() -handler = logging.StreamHandler(fake_stderr) -formatter = logging.Formatter( - '%(asctime)-15s WARNING] %(message)s') -handler.setFormatter(formatter) - -logging.captureWarnings(True) -logger = logging.getLogger('py.warnings') -logger.addHandler(handler) -logger.setLevel(logging.DEBUG) - -warnings.resetwarnings() -warnings.simplefilter('default') -warnings.warn('This will go to the logs output') - -print(fake_stderr.getvalue()) - -warnings.resetwarnings() - - -# Example 12 -with warnings.catch_warnings(record=True) as found_warnings: - found = require('my_arg', None, 'fake units') - expected = 'fake units' - assert found == expected - - -# Example 13 -assert len(found_warnings) == 1 -single_warning = found_warnings[0] -assert str(single_warning.message) == ( - 'my_arg will be required soon, update your code') -assert single_warning.category == DeprecationWarning diff --git a/example_code/item_90.py b/example_code/item_90.py deleted file mode 100755 index 792a8c7..0000000 --- a/example_code/item_90.py +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Reproduce book environment -import random -random.seed(1234) - -import logging -from pprint import pprint -from sys import stdout as STDOUT - -# Write all output to a temporary directory -import atexit -import gc -import io -import os -import tempfile - -TEST_DIR = tempfile.TemporaryDirectory() -atexit.register(TEST_DIR.cleanup) - -# Make sure Windows processes exit cleanly -OLD_CWD = os.getcwd() -atexit.register(lambda: os.chdir(OLD_CWD)) -os.chdir(TEST_DIR.name) - -def close_open_files(): - everything = gc.get_objects() - for obj in everything: - if isinstance(obj, io.IOBase): - obj.close() - -atexit.register(close_open_files) - - -# Example 1 -try: - def subtract(a, b): - return a - b - - subtract(10, '5') -except: - logging.exception('Expected') -else: - assert False - - -# Example 3 -try: - def concat(a, b): - return a + b - - concat('first', b'second') -except: - logging.exception('Expected') -else: - assert False - - -# Example 5 -class Counter: - def __init__(self): - self.value = 0 - - def add(self, offset): - value += offset - - def get(self) -> int: - self.value - - -# Example 6 -try: - counter = Counter() - counter.add(5) -except: - logging.exception('Expected') -else: - assert False - - -# Example 7 -try: - counter = Counter() - found = counter.get() - assert found == 0, found -except: - logging.exception('Expected') -else: - assert False - - -# Example 9 -try: - def combine(func, values): - assert len(values) > 0 - - result = values[0] - for next_value in values[1:]: - result = func(result, next_value) - - return result - - def add(x, y): - return x + y - - inputs = [1, 2, 3, 4j] - result = combine(add, inputs) - assert result == 10, result # Fails -except: - logging.exception('Expected') -else: - assert False - - -# Example 11 -try: - def get_or_default(value, default): - if value is not None: - return value - return value - - found = get_or_default(3, 5) - assert found == 3 - - found = get_or_default(None, 5) - assert found == 5, found # Fails -except: - logging.exception('Expected') -else: - assert False - - -# Example 13 -class FirstClass: - def __init__(self, value): - self.value = value - -class SecondClass: - def __init__(self, value): - self.value = value - -second = SecondClass(5) -first = FirstClass(second) - -del FirstClass -del SecondClass - - -# Example 15 -try: - class FirstClass: - def __init__(self, value: SecondClass) -> None: # Breaks - self.value = value - - class SecondClass: - def __init__(self, value: int) -> None: - self.value = value - - second = SecondClass(5) - first = FirstClass(second) -except: - logging.exception('Expected') -else: - assert False - - -# Example 16 -class FirstClass: - def __init__(self, value: 'SecondClass') -> None: # OK - self.value = value - -class SecondClass: - def __init__(self, value: int) -> None: - self.value = value - -second = SecondClass(5) -first = FirstClass(second) diff --git a/example_code/item_90_example_02.py b/example_code/item_90_example_02.py deleted file mode 100755 index 012cffa..0000000 --- a/example_code/item_90_example_02.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -# Example 2 -# Check types in this file with: python -m mypy - -def subtract(a: int, b: int) -> int: # Function annotation - return a - b - -subtract(10, '5') # Oops: passed string value diff --git a/example_code/item_90_example_04.py b/example_code/item_90_example_04.py deleted file mode 100755 index 6d6fe10..0000000 --- a/example_code/item_90_example_04.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -# Example 4 -# Check types in this file with: python -m mypy - -def concat(a: str, b: str) -> str: - return a + b - -concat('first', b'second') # Oops: passed bytes value diff --git a/example_code/item_90_example_08.py b/example_code/item_90_example_08.py deleted file mode 100755 index 09f42e6..0000000 --- a/example_code/item_90_example_08.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -# Example 8 -# Check types in this file with: python -m mypy - -class Counter: - def __init__(self) -> None: - self.value: int = 0 # Field / variable annotation - - def add(self, offset: int) -> None: - value += offset # Oops: forgot "self." - - def get(self) -> int: - self.value # Oops: forgot "return" - -counter = Counter() -counter.add(5) -counter.add(3) -assert counter.get() == 8 diff --git a/example_code/item_90_example_10.py b/example_code/item_90_example_10.py deleted file mode 100755 index 75ee1ca..0000000 --- a/example_code/item_90_example_10.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -# Example 10 -# Check types in this file with: python -m mypy - -from typing import Callable, List, TypeVar - -Value = TypeVar('Value') -Func = Callable[[Value, Value], Value] - -def combine(func: Func[Value], values: List[Value]) -> Value: - assert len(values) > 0 - - result = values[0] - for next_value in values[1:]: - result = func(result, next_value) - - return result - -Real = TypeVar('Real', int, float) - -def add(x: Real, y: Real) -> Real: - return x + y - -inputs = [1, 2, 3, 4j] # Oops: included a complex number -result = combine(add, inputs) -assert result == 10 diff --git a/example_code/item_90_example_12.py b/example_code/item_90_example_12.py deleted file mode 100755 index b0d98b9..0000000 --- a/example_code/item_90_example_12.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -# Example 12 -# Check types in this file with: python -m mypy - -from typing import Optional - -def get_or_default(value: Optional[int], - default: int) -> int: - if value is not None: - return value - return value # Oops: should have returned "default" diff --git a/example_code/item_90_example_14.py b/example_code/item_90_example_14.py deleted file mode 100755 index 1feacb8..0000000 --- a/example_code/item_90_example_14.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -# Example 14 -# Check types in this file with: python -m mypy - -class FirstClass: - def __init__(self, value: SecondClass) -> None: - self.value = value - -class SecondClass: - def __init__(self, value: int) -> None: - self.value = value - -second = SecondClass(5) -first = FirstClass(second) diff --git a/example_code/item_90_example_17.py b/example_code/item_90_example_17.py deleted file mode 100755 index b77f9fc..0000000 --- a/example_code/item_90_example_17.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env PYTHONHASHSEED=1234 python3 - -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - - -# Example 17 -from __future__ import annotations - -class FirstClass: - def __init__(self, value: SecondClass) -> None: # OK - self.value = value - -class SecondClass: - def __init__(self, value: int) -> None: - self.value = value - -second = SecondClass(5) -first = FirstClass(second) From f631e8937762f086c2a2eb0443142d8f81b02fa8 Mon Sep 17 00:00:00 2001 From: Brett Slatkin Date: Tue, 10 Dec 2024 14:59:07 -0800 Subject: [PATCH 09/12] Checking in latest examples --- example_code/item_001.py | 56 ++ example_code/item_003.py | 119 +++++ example_code/item_004.py | 113 ++++ example_code/item_005.py | 141 +++++ example_code/item_006.py | 151 ++++++ example_code/item_007.py | 178 +++++++ example_code/item_008.py | 242 +++++++++ example_code/item_009.py | 311 +++++++++++ example_code/item_010.py | 203 +++++++ example_code/item_011.py | 328 ++++++++++++ example_code/item_012.py | 130 +++++ example_code/item_013.py | 159 ++++++ example_code/item_014.py | 132 +++++ example_code/item_015.py | 106 ++++ example_code/item_016.py | 146 ++++++ example_code/item_017.py | 86 +++ example_code/item_018.py | 105 ++++ example_code/item_019.py | 114 ++++ example_code/item_020.py | 93 ++++ example_code/item_021.py | 213 ++++++++ example_code/item_022.py | 217 ++++++++ example_code/item_023.py | 140 +++++ example_code/item_024.py | 187 +++++++ example_code/item_025.py | 189 +++++++ example_code/item_025_example_01.py | 25 + example_code/item_025_example_03.py | 28 + example_code/item_025_example_05.py | 25 + example_code/item_025_example_07.py | 28 + example_code/item_025_example_17.py | 68 +++ example_code/item_026.py | 198 +++++++ example_code/item_027.py | 102 ++++ example_code/item_028.py | 192 +++++++ example_code/item_029.py | 230 ++++++++ example_code/item_030.py | 99 ++++ example_code/item_031.py | 181 +++++++ example_code/item_032.py | 125 +++++ example_code/item_032_example_09.py | 41 ++ example_code/item_033.py | 151 ++++++ example_code/item_034.py | 101 ++++ example_code/item_035.py | 169 ++++++ example_code/item_036.py | 129 +++++ example_code/item_036_example_09.py | 41 ++ example_code/item_037.py | 263 ++++++++++ example_code/item_038.py | 133 +++++ example_code/item_039.py | 132 +++++ example_code/item_040.py | 99 ++++ example_code/item_041.py | 102 ++++ example_code/item_042.py | 142 +++++ example_code/item_043.py | 115 ++++ example_code/item_044.py | 77 +++ example_code/item_045.py | 88 ++++ example_code/item_046.py | 171 ++++++ example_code/item_047.py | 177 +++++++ example_code/item_048.py | 139 +++++ example_code/item_049.py | 203 +++++++ example_code/item_050.py | 245 +++++++++ example_code/item_051.py | 487 +++++++++++++++++ example_code/item_051_example_04.py | 31 ++ example_code/item_051_example_05.py | 32 ++ example_code/item_052.py | 213 ++++++++ example_code/item_053.py | 170 ++++++ example_code/item_054.py | 183 +++++++ example_code/item_055.py | 217 ++++++++ example_code/item_056.py | 392 ++++++++++++++ example_code/item_056_example_11.py | 31 ++ example_code/item_056_example_12.py | 46 ++ example_code/item_057.py | 202 +++++++ example_code/item_058.py | 193 +++++++ example_code/item_059.py | 210 ++++++++ example_code/item_060.py | 233 ++++++++ example_code/item_061.py | 201 +++++++ example_code/item_062.py | 304 +++++++++++ example_code/item_063.py | 216 ++++++++ example_code/item_064.py | 185 +++++++ example_code/item_065.py | 305 +++++++++++ example_code/item_066.py | 273 ++++++++++ example_code/item_067.py | 188 +++++++ example_code/item_068.py | 146 ++++++ example_code/item_069.py | 156 ++++++ example_code/item_070.py | 382 ++++++++++++++ example_code/item_071.py | 237 +++++++++ example_code/item_072.py | 216 ++++++++ example_code/item_073.py | 413 +++++++++++++++ example_code/item_074.py | 210 ++++++++ example_code/item_075.py | 233 ++++++++ example_code/item_076.py | 496 ++++++++++++++++++ example_code/item_077.py | 305 +++++++++++ example_code/item_078.py | 262 +++++++++ example_code/item_079/parallel/my_module.py | 23 + .../item_079/parallel/run_parallel.py | 43 ++ example_code/item_079/parallel/run_serial.py | 41 ++ example_code/item_079/parallel/run_threads.py | 43 ++ example_code/item_080.py | 198 +++++++ example_code/item_081.py | 138 +++++ example_code/item_082.py | 146 ++++++ example_code/item_083.py | 108 ++++ example_code/item_084.py | 112 ++++ example_code/item_085.py | 109 ++++ example_code/item_086.py | 249 +++++++++ example_code/item_087.py | 129 +++++ example_code/item_088.py | 256 +++++++++ example_code/item_089.py | 171 ++++++ example_code/item_089_example_07.py | 38 ++ example_code/item_090.py | 94 ++++ example_code/item_091.py | 86 +++ example_code/item_092.py | 149 ++++++ example_code/item_093.py | 127 +++++ example_code/item_094.py | 57 ++ example_code/item_095.py | 143 +++++ .../my_extension/my_extension_test.py | 83 +++ example_code/item_096/my_extension/setup.py | 28 + .../my_extension2/my_extension2_test.py | 96 ++++ example_code/item_098/mycli/adjust.py | 21 + example_code/item_098/mycli/enhance.py | 24 + .../item_098/mycli/global_lock_perf.py | 41 ++ example_code/item_098/mycli/import_perf.py | 29 + example_code/item_098/mycli/mycli.py | 33 ++ example_code/item_098/mycli/mycli_faster.py | 35 ++ example_code/item_098/mycli/parser.py | 30 ++ example_code/item_098/mycli/server.py | 43 ++ example_code/item_099.py | 225 ++++++++ example_code/item_100.py | 172 ++++++ example_code/item_101.py | 76 +++ example_code/item_102.py | 123 +++++ example_code/item_103.py | 243 +++++++++ example_code/item_104.py | 366 +++++++++++++ example_code/item_105.py | 122 +++++ example_code/item_106.py | 100 ++++ example_code/item_107.py | 227 ++++++++ example_code/item_108/testing/assert_test.py | 32 ++ .../item_108/testing/data_driven_test.py | 42 ++ example_code/item_108/testing/helper_test.py | 59 +++ example_code/item_108/testing/utils.py | 23 + .../item_108/testing/utils_error_test.py | 30 ++ example_code/item_108/testing/utils_test.py | 31 ++ example_code/item_109.py | 213 ++++++++ .../item_110/testing/environment_test.py | 34 ++ .../item_110/testing/integration_test.py | 39 ++ example_code/item_111.py | 323 ++++++++++++ example_code/item_112.py | 168 ++++++ example_code/item_113.py | 97 ++++ .../item_114/debugging/always_breakpoint.py | 36 ++ .../debugging/conditional_breakpoint.py | 36 ++ example_code/item_114/debugging/my_module.py | 34 ++ .../debugging/postmortem_breakpoint.py | 35 ++ example_code/item_115/tracemalloc/top_n.py | 29 + example_code/item_115/tracemalloc/using_gc.py | 31 ++ .../item_115/tracemalloc/waste_memory.py | 35 ++ .../item_115/tracemalloc/with_trace.py | 30 ++ example_code/item_118.py | 115 ++++ example_code/item_118_example_06.py | 25 + example_code/item_118_example_07.py | 37 ++ .../item_119/api_package/api_consumer.py | 32 ++ example_code/item_119/api_package/main.py | 17 + example_code/item_119/api_package/main2.py | 17 + .../api_package/mypackage/__init__.py | 23 + .../item_119/api_package/mypackage/models.py | 22 + .../item_119/api_package/mypackage/utils.py | 27 + .../namespace_package/analysis/__init__.py | 17 + .../namespace_package/analysis/utils.py | 27 + .../namespace_package/frontend/__init__.py | 17 + .../namespace_package/frontend/utils.py | 24 + .../item_119/namespace_package/main.py | 21 + .../item_119/namespace_package/main2.py | 21 + .../item_119/namespace_package/main3.py | 22 + .../item_119/namespace_package/main4.py | 22 + example_code/item_120.py | 63 +++ .../item_120/module_scope/db_connection.py | 29 + .../item_120/module_scope/dev_main.py | 21 + .../item_120/module_scope/prod_main.py | 21 + example_code/item_121.py | 191 +++++++ .../item_122/recursive_import_bad/app.py | 24 + .../item_122/recursive_import_bad/dialog.py | 27 + .../item_122/recursive_import_bad/main.py | 17 + .../item_122/recursive_import_dynamic/app.py | 25 + .../recursive_import_dynamic/dialog.py | 33 ++ .../item_122/recursive_import_dynamic/main.py | 17 + .../recursive_import_nosideeffects/app.py | 27 + .../recursive_import_nosideeffects/dialog.py | 30 ++ .../recursive_import_nosideeffects/main.py | 23 + .../item_122/recursive_import_ordering/app.py | 26 + .../recursive_import_ordering/dialog.py | 30 ++ .../recursive_import_ordering/main.py | 17 + example_code/item_123.py | 276 ++++++++++ example_code/item_124.py | 180 +++++++ example_code/item_124_example_02.py | 25 + example_code/item_124_example_06.py | 35 ++ example_code/item_124_example_08.py | 44 ++ example_code/item_124_example_10.py | 25 + example_code/item_124_example_12.py | 31 ++ .../zipimport_examples/django_pkgutil.py | 24 + .../item_125/zipimport_examples/trans_real.py | 29 + 192 files changed, 22956 insertions(+) create mode 100755 example_code/item_001.py create mode 100755 example_code/item_003.py create mode 100755 example_code/item_004.py create mode 100755 example_code/item_005.py create mode 100755 example_code/item_006.py create mode 100755 example_code/item_007.py create mode 100755 example_code/item_008.py create mode 100755 example_code/item_009.py create mode 100755 example_code/item_010.py create mode 100755 example_code/item_011.py create mode 100755 example_code/item_012.py create mode 100755 example_code/item_013.py create mode 100755 example_code/item_014.py create mode 100755 example_code/item_015.py create mode 100755 example_code/item_016.py create mode 100755 example_code/item_017.py create mode 100755 example_code/item_018.py create mode 100755 example_code/item_019.py create mode 100755 example_code/item_020.py create mode 100755 example_code/item_021.py create mode 100755 example_code/item_022.py create mode 100755 example_code/item_023.py create mode 100755 example_code/item_024.py create mode 100755 example_code/item_025.py create mode 100755 example_code/item_025_example_01.py create mode 100755 example_code/item_025_example_03.py create mode 100755 example_code/item_025_example_05.py create mode 100755 example_code/item_025_example_07.py create mode 100755 example_code/item_025_example_17.py create mode 100755 example_code/item_026.py create mode 100755 example_code/item_027.py create mode 100755 example_code/item_028.py create mode 100755 example_code/item_029.py create mode 100755 example_code/item_030.py create mode 100755 example_code/item_031.py create mode 100755 example_code/item_032.py create mode 100755 example_code/item_032_example_09.py create mode 100755 example_code/item_033.py create mode 100755 example_code/item_034.py create mode 100755 example_code/item_035.py create mode 100755 example_code/item_036.py create mode 100755 example_code/item_036_example_09.py create mode 100755 example_code/item_037.py create mode 100755 example_code/item_038.py create mode 100755 example_code/item_039.py create mode 100755 example_code/item_040.py create mode 100755 example_code/item_041.py create mode 100755 example_code/item_042.py create mode 100755 example_code/item_043.py create mode 100755 example_code/item_044.py create mode 100755 example_code/item_045.py create mode 100755 example_code/item_046.py create mode 100755 example_code/item_047.py create mode 100755 example_code/item_048.py create mode 100755 example_code/item_049.py create mode 100755 example_code/item_050.py create mode 100755 example_code/item_051.py create mode 100755 example_code/item_051_example_04.py create mode 100755 example_code/item_051_example_05.py create mode 100755 example_code/item_052.py create mode 100755 example_code/item_053.py create mode 100755 example_code/item_054.py create mode 100755 example_code/item_055.py create mode 100755 example_code/item_056.py create mode 100755 example_code/item_056_example_11.py create mode 100755 example_code/item_056_example_12.py create mode 100755 example_code/item_057.py create mode 100755 example_code/item_058.py create mode 100755 example_code/item_059.py create mode 100755 example_code/item_060.py create mode 100755 example_code/item_061.py create mode 100755 example_code/item_062.py create mode 100755 example_code/item_063.py create mode 100755 example_code/item_064.py create mode 100755 example_code/item_065.py create mode 100755 example_code/item_066.py create mode 100755 example_code/item_067.py create mode 100755 example_code/item_068.py create mode 100755 example_code/item_069.py create mode 100755 example_code/item_070.py create mode 100755 example_code/item_071.py create mode 100755 example_code/item_072.py create mode 100755 example_code/item_073.py create mode 100755 example_code/item_074.py create mode 100755 example_code/item_075.py create mode 100755 example_code/item_076.py create mode 100755 example_code/item_077.py create mode 100755 example_code/item_078.py create mode 100755 example_code/item_079/parallel/my_module.py create mode 100755 example_code/item_079/parallel/run_parallel.py create mode 100755 example_code/item_079/parallel/run_serial.py create mode 100755 example_code/item_079/parallel/run_threads.py create mode 100755 example_code/item_080.py create mode 100755 example_code/item_081.py create mode 100755 example_code/item_082.py create mode 100755 example_code/item_083.py create mode 100755 example_code/item_084.py create mode 100755 example_code/item_085.py create mode 100755 example_code/item_086.py create mode 100755 example_code/item_087.py create mode 100755 example_code/item_088.py create mode 100755 example_code/item_089.py create mode 100755 example_code/item_089_example_07.py create mode 100755 example_code/item_090.py create mode 100755 example_code/item_091.py create mode 100755 example_code/item_092.py create mode 100755 example_code/item_093.py create mode 100755 example_code/item_094.py create mode 100755 example_code/item_095.py create mode 100755 example_code/item_096/my_extension/my_extension_test.py create mode 100755 example_code/item_096/my_extension/setup.py create mode 100755 example_code/item_096/my_extension2/my_extension2_test.py create mode 100755 example_code/item_098/mycli/adjust.py create mode 100755 example_code/item_098/mycli/enhance.py create mode 100755 example_code/item_098/mycli/global_lock_perf.py create mode 100755 example_code/item_098/mycli/import_perf.py create mode 100755 example_code/item_098/mycli/mycli.py create mode 100755 example_code/item_098/mycli/mycli_faster.py create mode 100755 example_code/item_098/mycli/parser.py create mode 100755 example_code/item_098/mycli/server.py create mode 100755 example_code/item_099.py create mode 100755 example_code/item_100.py create mode 100755 example_code/item_101.py create mode 100755 example_code/item_102.py create mode 100755 example_code/item_103.py create mode 100755 example_code/item_104.py create mode 100755 example_code/item_105.py create mode 100755 example_code/item_106.py create mode 100755 example_code/item_107.py create mode 100755 example_code/item_108/testing/assert_test.py create mode 100755 example_code/item_108/testing/data_driven_test.py create mode 100755 example_code/item_108/testing/helper_test.py create mode 100755 example_code/item_108/testing/utils.py create mode 100755 example_code/item_108/testing/utils_error_test.py create mode 100755 example_code/item_108/testing/utils_test.py create mode 100755 example_code/item_109.py create mode 100755 example_code/item_110/testing/environment_test.py create mode 100755 example_code/item_110/testing/integration_test.py create mode 100755 example_code/item_111.py create mode 100755 example_code/item_112.py create mode 100755 example_code/item_113.py create mode 100755 example_code/item_114/debugging/always_breakpoint.py create mode 100755 example_code/item_114/debugging/conditional_breakpoint.py create mode 100755 example_code/item_114/debugging/my_module.py create mode 100755 example_code/item_114/debugging/postmortem_breakpoint.py create mode 100755 example_code/item_115/tracemalloc/top_n.py create mode 100755 example_code/item_115/tracemalloc/using_gc.py create mode 100755 example_code/item_115/tracemalloc/waste_memory.py create mode 100755 example_code/item_115/tracemalloc/with_trace.py create mode 100755 example_code/item_118.py create mode 100755 example_code/item_118_example_06.py create mode 100755 example_code/item_118_example_07.py create mode 100755 example_code/item_119/api_package/api_consumer.py create mode 100755 example_code/item_119/api_package/main.py create mode 100755 example_code/item_119/api_package/main2.py create mode 100755 example_code/item_119/api_package/mypackage/__init__.py create mode 100755 example_code/item_119/api_package/mypackage/models.py create mode 100755 example_code/item_119/api_package/mypackage/utils.py create mode 100755 example_code/item_119/namespace_package/analysis/__init__.py create mode 100755 example_code/item_119/namespace_package/analysis/utils.py create mode 100755 example_code/item_119/namespace_package/frontend/__init__.py create mode 100755 example_code/item_119/namespace_package/frontend/utils.py create mode 100755 example_code/item_119/namespace_package/main.py create mode 100755 example_code/item_119/namespace_package/main2.py create mode 100755 example_code/item_119/namespace_package/main3.py create mode 100755 example_code/item_119/namespace_package/main4.py create mode 100755 example_code/item_120.py create mode 100755 example_code/item_120/module_scope/db_connection.py create mode 100755 example_code/item_120/module_scope/dev_main.py create mode 100755 example_code/item_120/module_scope/prod_main.py create mode 100755 example_code/item_121.py create mode 100755 example_code/item_122/recursive_import_bad/app.py create mode 100755 example_code/item_122/recursive_import_bad/dialog.py create mode 100755 example_code/item_122/recursive_import_bad/main.py create mode 100755 example_code/item_122/recursive_import_dynamic/app.py create mode 100755 example_code/item_122/recursive_import_dynamic/dialog.py create mode 100755 example_code/item_122/recursive_import_dynamic/main.py create mode 100755 example_code/item_122/recursive_import_nosideeffects/app.py create mode 100755 example_code/item_122/recursive_import_nosideeffects/dialog.py create mode 100755 example_code/item_122/recursive_import_nosideeffects/main.py create mode 100755 example_code/item_122/recursive_import_ordering/app.py create mode 100755 example_code/item_122/recursive_import_ordering/dialog.py create mode 100755 example_code/item_122/recursive_import_ordering/main.py create mode 100755 example_code/item_123.py create mode 100755 example_code/item_124.py create mode 100755 example_code/item_124_example_02.py create mode 100755 example_code/item_124_example_06.py create mode 100755 example_code/item_124_example_08.py create mode 100755 example_code/item_124_example_10.py create mode 100755 example_code/item_124_example_12.py create mode 100755 example_code/item_125/zipimport_examples/django_pkgutil.py create mode 100755 example_code/item_125/zipimport_examples/trans_real.py diff --git a/example_code/item_001.py b/example_code/item_001.py new file mode 100755 index 0000000..d6a34cf --- /dev/null +++ b/example_code/item_001.py @@ -0,0 +1,56 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import sys + +print(sys.platform) +print(sys.implementation.name) +print(sys.version_info) +print(sys.version) diff --git a/example_code/item_003.py b/example_code/item_003.py new file mode 100755 index 0000000..34b3fa5 --- /dev/null +++ b/example_code/item_003.py @@ -0,0 +1,119 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + # This will not compile + source = """if True # Bad syntax + print('hello')""" + eval(source) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +try: + # This will not compile + source = """1.3j5 # Bad number""" + eval(source) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +def bad_reference(): + print(my_var) + my_var = 123 + + +print("Example 4") +try: + bad_reference() +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +def sometimes_ok(x): + if x: + my_var = 123 + print(my_var) + + +print("Example 6") +sometimes_ok(True) + + +print("Example 7") +try: + sometimes_ok(False) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +def bad_math(): + return 1 / 0 + + +print("Example 9") +try: + bad_math() +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_004.py b/example_code/item_004.py new file mode 100755 index 0000000..2ff06ec --- /dev/null +++ b/example_code/item_004.py @@ -0,0 +1,113 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +from urllib.parse import parse_qs + +my_values = parse_qs("red=5&blue=0&green=", keep_blank_values=True) +print(repr(my_values)) + + +print("Example 2") +print("Red: ", my_values.get("red")) +print("Green: ", my_values.get("green")) +print("Opacity:", my_values.get("opacity")) + + +print("Example 3") +# For query string 'red=5&blue=0&green=' +red = my_values.get("red", [""])[0] or 0 +green = my_values.get("green", [""])[0] or 0 +opacity = my_values.get("opacity", [""])[0] or 0 +print(f"Red: {red!r}") +print(f"Green: {green!r}") +print(f"Opacity: {opacity!r}") + + +print("Example 4") +red = int(my_values.get("red", [""])[0] or 0) +green = int(my_values.get("green", [""])[0] or 0) +opacity = int(my_values.get("opacity", [""])[0] or 0) +print(f"Red: {red!r}") +print(f"Green: {green!r}") +print(f"Opacity: {opacity!r}") + + +print("Example 5") +red_str = my_values.get("red", [""]) +red = int(red_str[0]) if red_str[0] else 0 +green_str = my_values.get("green", [""]) +green = int(green_str[0]) if green_str[0] else 0 +opacity_str = my_values.get("opacity", [""]) +opacity = int(opacity_str[0]) if opacity_str[0] else 0 +print(f"Red: {red!r}") +print(f"Green: {green!r}") +print(f"Opacity: {opacity!r}") + + +print("Example 6") +green_str = my_values.get("green", [""]) +if green_str[0]: + green = int(green_str[0]) +else: + green = 0 +print(f"Green: {green!r}") + + +print("Example 7") +def get_first_int(values, key, default=0): + found = values.get(key, [""]) + if found[0]: + return int(found[0]) + return default + + +print("Example 8") +green = get_first_int(my_values, "green") +print(f"Green: {green!r}") diff --git a/example_code/item_005.py b/example_code/item_005.py new file mode 100755 index 0000000..f0964ba --- /dev/null +++ b/example_code/item_005.py @@ -0,0 +1,141 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +no_snack = () +snack = ("chips",) + + +print("Example 2") +snack_calories = { + "chips": 140, + "popcorn": 80, + "nuts": 190, +} +items = list(snack_calories.items()) +print(items) + + +print("Example 3") +item = ("Peanut butter", "Jelly") +first_item = item[0] # Index +first_half = item[:1] # Slice +print(first_item) +print(first_half) + + +print("Example 4") +try: + pair = ("Chocolate", "Peanut butter") + pair[0] = "Honey" +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +item = ("Peanut butter", "Jelly") +first, second = item # Unpacking +print(first, "and", second) + + +print("Example 6") +favorite_snacks = { + "salty": ("pretzels", 100), + "sweet": ("cookies", 180), + "veggie": ("carrots", 20), +} +((type1, (name1, cals1)), + (type2, (name2, cals2)), + (type3, (name3, cals3))) = favorite_snacks.items() + +print(f"Favorite {type1} is {name1} with {cals1} calories") +print(f"Favorite {type2} is {name2} with {cals2} calories") +print(f"Favorite {type3} is {name3} with {cals3} calories") + + +print("Example 7") +def bubble_sort(a): + for _ in range(len(a)): + for i in range(1, len(a)): + if a[i] < a[i - 1]: + temp = a[i] + a[i] = a[i - 1] + a[i - 1] = temp + +names = ["pretzels", "carrots", "arugula", "bacon"] +bubble_sort(names) +print(names) + + +print("Example 8") +def bubble_sort(a): + for _ in range(len(a)): + for i in range(1, len(a)): + if a[i] < a[i - 1]: + a[i - 1], a[i] = a[i], a[i - 1] # Swap + +names = ["pretzels", "carrots", "arugula", "bacon"] +bubble_sort(names) +print(names) + + +print("Example 9") +snacks = [("bacon", 350), ("donut", 240), ("muffin", 190)] +for i in range(len(snacks)): + item = snacks[i] + name = item[0] + calories = item[1] + print(f"#{i+1}: {name} has {calories} calories") + + +print("Example 10") +for rank, (name, calories) in enumerate(snacks, 1): + print(f"#{rank}: {name} has {calories} calories") diff --git a/example_code/item_006.py b/example_code/item_006.py new file mode 100755 index 0000000..ac940b4 --- /dev/null +++ b/example_code/item_006.py @@ -0,0 +1,151 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +first = (1, 2, 3) + + +print("Example 2") +second = (1, 2, 3,) +second_wrapped = ( + 1, + 2, + 3, # Optional comma +) + + +print("Example 3") +third = 1, 2, 3 + + +print("Example 4") +fourth = 1, 2, 3, + + +print("Example 5") +assert first == second == third == fourth + + +print("Example 6") +empty = () + + +print("Example 7") +single_with = (1,) +single_without = (1) +assert single_with != single_without +assert single_with[0] == single_without + + +print("Example 8") +single_parens = (1,) +single_no_parens = 1, +assert single_parens == single_no_parens + + +print("Example 9") +def calculate_refund(a, b, c): + return 123_000_000 + +def get_order_value(a, b): + pass + +def get_tax(a, b): + pass + +def adjust_discount(a): + return 1 + +import types +user = types.SimpleNamespace(address='Fake address') +order = types.SimpleNamespace( + id='my order', + dest='my destination') +to_refund = calculate_refund( + get_order_value(user, order.id), + get_tax(user.address, order.dest), + adjust_discount(user) + 0.1), + + +print("Example 10") +print(type(to_refund)) + + +print("Example 11") +to_refund2 = calculate_refund( + get_order_value(user, order.id), + get_tax(user.address, order.dest), + adjust_discount(user) + 0.1, +) # No trailing comma +print(type(to_refund2)) + + +print("Example 12") +value_a = 1, # No parentheses, right +list_b = [1,] # No parentheses, wrong +list_c = [(1,)] # Parentheses, right +print('A:', value_a) +print('B:', list_b) +print('C:', list_c) + + +print("Example 13") +def get_coupon_codes(user): + return [['DEAL20']] + +(a1,), = get_coupon_codes(user) +(a2,) = get_coupon_codes(user) +(a3), = get_coupon_codes(user) +(a4) = get_coupon_codes(user) +a5, = get_coupon_codes(user) +a6 = get_coupon_codes(user) + +assert a1 not in (a2, a3, a4, a5, a6) +assert a2 == a3 == a5 +assert a4 == a6 diff --git a/example_code/item_007.py b/example_code/item_007.py new file mode 100755 index 0000000..c76bebc --- /dev/null +++ b/example_code/item_007.py @@ -0,0 +1,178 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +i = 3 +x = "even" if i % 2 == 0 else "odd" +print(x) + + +print("Example 2") +def fail(): + raise Exception("Oops") + +x = fail() if False else 20 +print(x) + + +print("Example 3") +result = [x / 4 for x in range(10) if x % 2 == 0] +print(result) + + +print("Example 4") +x = (i % 2 == 0 and "even") or "odd" + + +print("Example 5") +if i % 2 == 0: + x = "even" +else: + x = "odd" + + +print("Example 6") +if i % 2 == 0: + x = "even" + print("It was even!") # Added +else: + x = "odd" + + +print("Example 7") +if i % 2 == 0: + x = "even" +elif i % 3 == 0: # Added + x = "divisible by three" +else: + x = "odd" + + +print("Example 8") +def number_group(i): + if i % 2 == 0: + return "even" + else: + return "odd" + +x = number_group(i) # Short call +print(x) + + +print("Example 9") +def my_long_function_call(*args): + pass + +def my_other_long_function_call(*args): + pass + + +x = (my_long_function_call(1, 2, 3) if i % 2 == 0 + else my_other_long_function_call(4, 5, 6)) + + +print("Example 10") +x = ( + my_long_function_call(1, 2, 3) + if i % 2 == 0 + else my_other_long_function_call(4, 5, 6) +) + + +print("Example 11") +x = 2 +y = 1 + +if x and (z := x > y): + pass + + +print("Example 12") +try: + # This will not compile + source = """if x and z := x > y: + pass""" + eval(source) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 13") +if x > y if z else w: # Ambiguous + pass + +if x > (y if z else w): # Clear + pass + + +print("Example 14") +z = dict( + your_value=(y := 1), +) + + +print("Example 15") +try: + # This will not compile + source = """w = dict( + other_value=y := 1, + ) """ + eval(source) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 16") +v = dict( + my_value=1 if x else 3, +) diff --git a/example_code/item_008.py b/example_code/item_008.py new file mode 100755 index 0000000..b4ac6da --- /dev/null +++ b/example_code/item_008.py @@ -0,0 +1,242 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +fresh_fruit = { + "apple": 10, + "banana": 8, + "lemon": 5, +} + + +print("Example 2") +def make_lemonade(count): + print(f"Making {count} lemons into lemonade") + +def out_of_stock(): + print("Out of stock!") + +count = fresh_fruit.get("lemon", 0) +if count: + make_lemonade(count) +else: + out_of_stock() + + +print("Example 3") +if count := fresh_fruit.get("lemon", 0): + make_lemonade(count) +else: + out_of_stock() + + +print("Example 4") +def make_cider(count): + print(f"Making cider with {count} apples") + +count = fresh_fruit.get("apple", 0) +if count >= 4: + make_cider(count) +else: + out_of_stock() + + +print("Example 5") +if (count := fresh_fruit.get("apple", 0)) >= 4: + make_cider(count) +else: + out_of_stock() + + +print("Example 6") +def slice_bananas(count): + print(f"Slicing {count} bananas") + return count * 4 + +class OutOfBananas(Exception): + pass + +def make_smoothies(count): + print(f"Making smoothies with {count} banana slices") + +pieces = 0 +count = fresh_fruit.get("banana", 0) +if count >= 2: + pieces = slice_bananas(count) + +try: + smoothies = make_smoothies(pieces) +except OutOfBananas: + out_of_stock() + + +print("Example 7") +count = fresh_fruit.get("banana", 0) +if count >= 2: + pieces = slice_bananas(count) +else: + pieces = 0 # Moved + +try: + smoothies = make_smoothies(pieces) +except OutOfBananas: + out_of_stock() + + +print("Example 8") +pieces = 0 +if (count := fresh_fruit.get("banana", 0)) >= 2: # Changed + pieces = slice_bananas(count) + +try: + smoothies = make_smoothies(pieces) +except OutOfBananas: + out_of_stock() + + +print("Example 9") +if (count := fresh_fruit.get("banana", 0)) >= 2: + pieces = slice_bananas(count) +else: + pieces = 0 # Moved + +try: + smoothies = make_smoothies(pieces) +except OutOfBananas: + out_of_stock() + + +print("Example 10") +count = fresh_fruit.get("banana", 0) +if count >= 2: + pieces = slice_bananas(count) + to_enjoy = make_smoothies(pieces) +else: + count = fresh_fruit.get("apple", 0) + if count >= 4: + to_enjoy = make_cider(count) + else: + count = fresh_fruit.get("lemon", 0) + if count: + to_enjoy = make_lemonade(count) + else: + to_enjoy = "Nothing" + + +print("Example 11") +if (count := fresh_fruit.get("banana", 0)) >= 2: + pieces = slice_bananas(count) + to_enjoy = make_smoothies(pieces) +elif (count := fresh_fruit.get("apple", 0)) >= 4: + to_enjoy = make_cider(count) +elif count := fresh_fruit.get("lemon", 0): + to_enjoy = make_lemonade(count) +else: + to_enjoy = "Nothing" + + +print("Example 12") +FRUIT_TO_PICK = [ + {"apple": 1, "banana": 3}, + {"lemon": 2, "lime": 5}, + {"orange": 3, "melon": 2}, +] + +def pick_fruit(): + if FRUIT_TO_PICK: + return FRUIT_TO_PICK.pop(0) + else: + return [] + +def make_juice(fruit, count): + return [(fruit, count)] + +bottles = [] +fresh_fruit = pick_fruit() +while fresh_fruit: + for fruit, count in fresh_fruit.items(): + batch = make_juice(fruit, count) + bottles.extend(batch) + fresh_fruit = pick_fruit() + +print(bottles) + + +print("Example 13") +FRUIT_TO_PICK = [ + {"apple": 1, "banana": 3}, + {"lemon": 2, "lime": 5}, + {"orange": 3, "melon": 2}, +] +bottles = [] +while True: # Loop + fresh_fruit = pick_fruit() + if not fresh_fruit: # And a half + break + for fruit, count in fresh_fruit.items(): + batch = make_juice(fruit, count) + bottles.extend(batch) + +print(bottles) + + +print("Example 14") +FRUIT_TO_PICK = [ + {"apple": 1, "banana": 3}, + {"lemon": 2, "lime": 5}, + {"orange": 3, "melon": 2}, +] + +bottles = [] +while fresh_fruit := pick_fruit(): # Changed + for fruit, count in fresh_fruit.items(): + batch = make_juice(fruit, count) + bottles.extend(batch) + +print(bottles) diff --git a/example_code/item_009.py b/example_code/item_009.py new file mode 100755 index 0000000..dc5c1d6 --- /dev/null +++ b/example_code/item_009.py @@ -0,0 +1,311 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def take_action(light): + if light == "red": + print("Stop") + elif light == "yellow": + print("Slow down") + elif light == "green": + print("Go!") + else: + raise RuntimeError + + +print("Example 2") +take_action("red") +take_action("yellow") +take_action("green") + + +print("Example 3") +def take_match_action(light): + match light: + case "red": + print("Stop") + case "yellow": + print("Slow down") + case "green": + print("Go!") + case _: + raise RuntimeError + + +take_match_action("red") +take_match_action("yellow") +take_match_action("green") + + +print("Example 4") +try: + # This will not compile + source = """# Added these constants + RED = "red" + YELLOW = "yellow" + GREEN = "green" + + def take_constant_action(light): + match light: + case RED: # Changed + print("Stop") + case YELLOW: # Changed + print("Slow down") + case GREEN: # Changed + print("Go!") + case _: + raise RuntimeError""" + eval(source) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +RED = "red" +YELLOW = "yellow" +GREEN = "green" + +def take_truncated_action(light): + match light: + case RED: + print("Stop") + + +print("Example 6") +take_truncated_action(GREEN) + + +print("Example 7") +def take_debug_action(light): + match light: + case RED: + print(f"{RED=}, {light=}") + +take_debug_action(GREEN) + + +print("Example 8") +def take_unpacking_action(light): + try: + (RED,) = (light,) + except TypeError: + # Did not match + pass + else: + # Matched + print(f"{RED=}, {light=}") + + +take_unpacking_action(GREEN) + + +print("Example 9") +import enum # Added + +class ColorEnum(enum.Enum): # Added + RED = "red" + YELLOW = "yellow" + GREEN = "green" + +def take_enum_action(light): + match light: + case ColorEnum.RED: # Changed + print("Stop") + case ColorEnum.YELLOW: # Changed + print("Slow down") + case ColorEnum.GREEN: # Changed + print("Go!") + case _: + raise RuntimeError + +take_enum_action(ColorEnum.RED) +take_enum_action(ColorEnum.YELLOW) +take_enum_action(ColorEnum.GREEN) + + +print("Example 10") +for index, value in enumerate("abc"): + print(f"index {index} is {value}") + + +print("Example 11") +my_tree = (10, (7, None, 9), (13, 11, None)) + + +print("Example 12") +def contains(tree, value): + if not isinstance(tree, tuple): + return tree == value + + pivot, left, right = tree + + if value < pivot: + return contains(left, value) + elif value > pivot: + return contains(right, value) + else: + return value == pivot + + +print("Example 13") +assert contains(my_tree, 9) +assert not contains(my_tree, 14) + +for i in range(0, 14): + print(i, contains(my_tree, i)) + + +print("Example 14") +def contains_match(tree, value): + match tree: + case pivot, left, _ if value < pivot: + return contains_match(left, value) + case pivot, _, right if value > pivot: + return contains_match(right, value) + case (pivot, _, _) | pivot: + return pivot == value + + +assert contains_match(my_tree, 9) +assert not contains_match(my_tree, 14) + +for i in range(0, 14): + print(i, contains_match(my_tree, i)) + + +print("Example 15") +class Node: + def __init__(self, value, left=None, right=None): + self.value = value + self.left = left + self.right = right + + +print("Example 16") +obj_tree = Node( + value=10, + left=Node(value=7, right=9), + right=Node(value=13, left=11), +) + + +print("Example 17") +def contains_class(tree, value): + if not isinstance(tree, Node): + return tree == value + elif value < tree.value: + return contains_class(tree.left, value) + elif value > tree.value: + return contains_class(tree.right, value) + else: + return tree.value == value + + +assert contains_class(obj_tree, 9) +assert not contains_class(obj_tree, 14) + +for i in range(0, 14): + print(i, contains_class(obj_tree, i)) + + +print("Example 18") +def contains_match_class(tree, value): + match tree: + case Node(value=pivot, left=left) if value < pivot: + return contains_match_class(left, value) + case Node(value=pivot, right=right) if value > pivot: + return contains_match_class(right, value) + case Node(value=pivot) | pivot: + return pivot == value + + +assert contains_match_class(obj_tree, 9) +assert not contains_match_class(obj_tree, 14) + +for i in range(0, 14): + print(i, contains_match_class(obj_tree, i)) + + +print("Example 19") +record1 = """{"customer": {"last": "Ross", "first": "Bob"}}""" +record2 = """{"customer": {"entity": "Steve's Painting Co."}}""" + + +print("Example 20") +from dataclasses import dataclass + +@dataclass +class PersonCustomer: + first_name: str + last_name: str + +@dataclass +class BusinessCustomer: + company_name: str + + +print("Example 21") +import json + +def deserialize(data): + record = json.loads(data) + match record: + case {"customer": {"last": last_name, "first": first_name}}: + return PersonCustomer(first_name, last_name) + case {"customer": {"entity": company_name}}: + return BusinessCustomer(company_name) + case _: + raise ValueError("Unknown record type") + + +print("Example 22") +print("Record1:", deserialize(record1)) +print("Record2:", deserialize(record2)) diff --git a/example_code/item_010.py b/example_code/item_010.py new file mode 100755 index 0000000..0974798 --- /dev/null +++ b/example_code/item_010.py @@ -0,0 +1,203 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +a = b"h\x65llo" +print(type(a)) +print(list(a)) +print(a) + + +print("Example 2") +a = "a\u0300 propos" +print(type(a)) +print(list(a)) +print(a) + + +print("Example 3") +def to_str(bytes_or_str): + if isinstance(bytes_or_str, bytes): + value = bytes_or_str.decode("utf-8") + else: + value = bytes_or_str + return value # Instance of str + +print(repr(to_str(b"foo"))) +print(repr(to_str("bar"))) + + +print("Example 4") +def to_bytes(bytes_or_str): + if isinstance(bytes_or_str, str): + value = bytes_or_str.encode("utf-8") + else: + value = bytes_or_str + return value # Instance of bytes + +print(repr(to_bytes(b"foo"))) +print(repr(to_bytes("bar"))) + + +print("Example 5") +print(b"one" + b"two") +print("one" + "two") + + +print("Example 6") +try: + b"one" + "two" +except: + logging.exception('Expected') +else: + assert False + + +print("Example 7") +try: + "one" + b"two" +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +assert b"red" > b"blue" +assert "red" > "blue" + + +print("Example 9") +try: + assert "red" > b"blue" +except: + logging.exception('Expected') +else: + assert False + + +print("Example 10") +try: + assert b"blue" < "red" +except: + logging.exception('Expected') +else: + assert False + + +print("Example 11") +print(b"foo" == "foo") + + +print("Example 12") +blue_bytes = b"blue" +blue_str = "blue" +print(b"red %s" % blue_bytes) +print("red %s" % blue_str) + + +print("Example 13") +try: + print(b"red %s" % blue_str) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 14") +print("red %s" % blue_bytes) +print(f"red {blue_bytes}") + + +print("Example 15") +try: + with open("data.bin", "w") as f: + f.write(b"\xf1\xf2\xf3\xf4\xf5") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 16") +with open("data.bin", "wb") as f: + f.write(b"\xf1\xf2\xf3\xf4\xf5") + + +print("Example 17") +try: + # Silently force UTF-8 here to make sure this test fails on + # all platforms. cp1252 considers these bytes valid on Windows. + real_open = open + + def open(*args, **kwargs): + kwargs["encoding"] = "utf-8" + return real_open(*args, **kwargs) + + with open("data.bin", "r") as f: + data = f.read() +except: + logging.exception('Expected') +else: + assert False + + +print("Example 18") +# Restore the overloaded open above. +open = real_open +with open("data.bin", "rb") as f: + data = f.read() +assert data == b"\xf1\xf2\xf3\xf4\xf5" + + +print("Example 19") +with open("data.bin", "r", encoding="cp1252") as f: + data = f.read() +assert data == "ñòóôõ" diff --git a/example_code/item_011.py b/example_code/item_011.py new file mode 100755 index 0000000..6dae477 --- /dev/null +++ b/example_code/item_011.py @@ -0,0 +1,328 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +a = 0b10111011 +b = 0xC5F +print("Binary is %d, hex is %d" % (a, b)) + + +print("Example 2") +key = "my_var" +value = 1.234 +formatted = "%-10s = %.2f" % (key, value) +print(formatted) + + +print("Example 3") +try: + reordered_tuple = "%-10s = %.2f" % (value, key) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 4") +try: + reordered_string = "%.2f = %-10s" % (key, value) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +pantry = [ + ("avocados", 1.25), + ("bananas", 2.5), + ("cherries", 15), +] +for i, (item, count) in enumerate(pantry): + print("#%d: %-10s = %.2f" % (i, item, count)) + + +print("Example 6") +for i, (item, count) in enumerate(pantry): + print( + "#%d: %-10s = %d" + % ( + i + 1, + item.title(), + round(count), + ) + ) + + +print("Example 7") +template = "%s loves food. See %s cook." +name = "Max" +formatted = template % (name, name) +print(formatted) + + +print("Example 8") +name = "brad" +formatted = template % (name.title(), name) +print(formatted) + + +print("Example 9") +key = "my_var" +value = 1.234 + +old_way = "%-10s = %.2f" % (key, value) + +new_way = "%(key)-10s = %(value).2f" % { + "key": key, # Key first + "value": value, +} + +reordered = "%(key)-10s = %(value).2f" % { + "value": value, + "key": key, # Key second +} + +assert old_way == new_way == reordered + + +print("Example 10") +name = "Max" + +template = "%s loves food. See %s cook." +before = template % (name, name) # Tuple + +template = "%(name)s loves food. See %(name)s cook." +after = template % {"name": name} # Dictionary + +assert before == after + + +print("Example 11") +for i, (item, count) in enumerate(pantry): + before = "#%d: %-10s = %d" % ( + i + 1, + item.title(), + round(count), + ) + + after = "#%(loop)d: %(item)-10s = %(count)d" % { + "loop": i + 1, + "item": item.title(), + "count": round(count), + } + + assert before == after + + +print("Example 12") +soup = "lentil" +formatted = "Today's soup is %(soup)s." % {"soup": soup} +print(formatted) + + +print("Example 13") +menu = { + "soup": "lentil", + "oyster": "kumamoto", + "special": "schnitzel", +} +template = ( + "Today's soup is %(soup)s, " + "buy one get two %(oyster)s oysters, " + "and our special entrée is %(special)s." +) +formatted = template % menu +print(formatted) + + +print("Example 14") +a = 1234.5678 +formatted = format(a, ",.2f") +print(formatted) + +b = "my string" +formatted = format(b, "^20s") +print("*", formatted, "*") + + +print("Example 15") +key = "my_var" +value = 1.234 + +formatted = "{} = {}".format(key, value) +print(formatted) + + +print("Example 16") +formatted = "{:<10} = {:.2f}".format(key, value) +print(formatted) + + +print("Example 17") +print("%.2f%%" % 12.5) +print("{} replaces {{}}".format(1.23)) + + +print("Example 18") +formatted = "{1} = {0}".format(key, value) +print(formatted) + + +print("Example 19") +formatted = "{0} loves food. See {0} cook.".format(name) +print(formatted) + + +print("Example 20") +for i, (item, count) in enumerate(pantry): + old_style = "#%d: %-10s = %d" % ( + i + 1, + item.title(), + round(count), + ) + + new_style = "#{}: {:<10s} = {}".format( + i + 1, + item.title(), + round(count), + ) + + assert old_style == new_style + + +print("Example 21") +formatted = "First letter is {menu[oyster][0]!r}".format(menu=menu) +print(formatted) + + +print("Example 22") +old_template = ( + "Today's soup is %(soup)s, " + "buy one get two %(oyster)s oysters, " + "and our special entrée is %(special)s." +) +old_formatted = old_template % { + "soup": "lentil", + "oyster": "kumamoto", + "special": "schnitzel", +} + +new_template = ( + "Today's soup is {soup}, " + "buy one get two {oyster} oysters, " + "and our special entrée is {special}." +) +new_formatted = new_template.format( + soup="lentil", + oyster="kumamoto", + special="schnitzel", +) + +assert old_formatted == new_formatted + + +print("Example 23") +key = "my_var" +value = 1.234 + +formatted = f"{key} = {value}" +print(formatted) + + +print("Example 24") +formatted = f"{key!r:<10} = {value:.2f}" +print(formatted) + + +print("Example 25") +f_string = f"{key:<10} = {value:.2f}" + +c_tuple = "%-10s = %.2f" % (key, value) + +str_args = "{:<10} = {:.2f}".format(key, value) + +str_kw = "{key:<10} = {value:.2f}".format(key=key, value=value) + +c_dict = "%(key)-10s = %(value).2f" % {"key": key, "value": value} + +assert c_tuple == c_dict == f_string +assert str_args == str_kw == f_string + + +print("Example 26") +for i, (item, count) in enumerate(pantry): + old_style = "#%d: %-10s = %d" % ( + i + 1, + item.title(), + round(count), + ) + + new_style = "#{}: {:<10s} = {}".format( + i + 1, + item.title(), + round(count), + ) + + f_string = f"#{i+1}: {item.title():<10s} = {round(count)}" + + assert old_style == new_style == f_string + + +print("Example 27") +for i, (item, count) in enumerate(pantry): + print(f"#{i+1}: " + f"{item.title():<10s} = " + f"{round(count)}") + + +print("Example 28") +places = 3 +number = 1.23456 +print(f"My number is {number:.{places}f}") diff --git a/example_code/item_012.py b/example_code/item_012.py new file mode 100755 index 0000000..3a43859 --- /dev/null +++ b/example_code/item_012.py @@ -0,0 +1,130 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +print("foo bar") + + +print("Example 2") +my_value = "foo bar" +print(str(my_value)) +print("%s" % my_value) +print(f"{my_value}") +print(format(my_value)) +print(my_value.__format__("s")) +print(my_value.__str__()) + + +print("Example 3") +int_value = 5 +str_value = "5" +print(int_value) +print(str_value) +print(f"Is {int_value} == {str_value}?") + + +print("Example 4") +a = "\x07" +print(repr(a)) + + +print("Example 5") +b = eval(repr(a)) +assert a == b + + +print("Example 6") +print(repr(int_value)) +print(repr(str_value)) + + +print("Example 7") +print("Is %r == %r?" % (int_value, str_value)) +print(f"Is {int_value!r} == {str_value!r}?") + + +print("Example 8") +class OpaqueClass: + def __init__(self, x, y): + self.x = x + self.y = y + +obj = OpaqueClass(1, "foo") +print(obj) + + +print("Example 9") +class BetterClass: + def __init__(self, x, y): + self.x = x + self.y = y + + def __repr__(self): + return f"BetterClass({self.x!r}, {self.y!r})" + + +print("Example 10") +obj = BetterClass(2, "bar") +print(obj) + + +print("Example 11") +print(str(obj)) + + +print("Example 12") +class StringifiableBetterClass(BetterClass): + def __str__(self): + return f"({self.x}, {self.y})" + + +print("Example 13") +obj2 = StringifiableBetterClass(2, "bar") +print("Human readable:", obj2) +print("Printable: ", repr(obj2)) diff --git a/example_code/item_013.py b/example_code/item_013.py new file mode 100755 index 0000000..e78ec9a --- /dev/null +++ b/example_code/item_013.py @@ -0,0 +1,159 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +my_test1 = "hello" "world" +my_test2 = "hello" + "world" +assert my_test1 == my_test2 + + +print("Example 2") +x = 1 +my_test1 = ( + r"first \ part is here with escapes\n, " + f"string interpolation {x} in here, " + 'this has "double quotes" inside' +) +print(my_test1) + + +print("Example 3") +y = 2 +my_test2 = r"fir\st" f"{y}" '"third"' +print(my_test2) + + +print("Example 4") +my_test3 = r"fir\st", f"{y}" '"third"' +print(my_test3) + + +print("Example 5") +my_test4 = [ + "first line\n", + "second line\n", + "third line\n", +] +print(my_test4) + + +print("Example 6") +my_test5 = [ + "first line\n", + "second line\n" # Comma removed + "third line\n", +] +print(my_test5) + + +print("Example 7") +my_test5 = [ + "first line\n", + "second line\n" "third line\n", +] + + +print("Example 8") +my_test6 = [ + "first line\n", + "second line\n" + # Explicit + "third line\n", +] +assert my_test5 == my_test6 + + +print("Example 9") +my_test6 = [ + "first line\n", + "second line\n" + "third line\n", +] + + +print("Example 10") +print("this is my long message " + "that should be printed out") + + +print("Example 11") +import sys + +print("this is my long message " + "that should be printed out", + end="", + file=sys.stderr) + + +print("Example 12") +import sys + +first_value = ... +second_value = ... + +class MyData: + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + +value = MyData(123, + first_value, + f"my format string {x}" + f"another value {y}", + "and here is more text", + second_value, + stream=sys.stderr) + + +print("Example 13") +value2 = MyData(123, + first_value, + f"my format string {x}" + # Explicit + f"another value {y}", + "and here is more text", + second_value, + stream=sys.stderr) diff --git a/example_code/item_014.py b/example_code/item_014.py new file mode 100755 index 0000000..2902a15 --- /dev/null +++ b/example_code/item_014.py @@ -0,0 +1,132 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +a = ["a", "b", "c", "d", "e", "f", "g", "h"] +print("Middle two: ", a[3:5]) +print("All but ends:", a[1:7]) + + +print("Example 2") +assert a[:5] == a[0:5] + + +print("Example 3") +assert a[5:] == a[5:len(a)] + + +print("Example 4") +print(a[:]) +print(a[:5]) +print(a[:-1]) +print(a[4:]) +print(a[-3:]) +print(a[2:5]) +print(a[2:-1]) +print(a[-3:-1]) + + +print("Example 5") +a[:] # ["a", "b", "c", "d", "e", "f", "g", "h"] +a[:5] # ["a", "b", "c", "d", "e"] +a[:-1] # ["a", "b", "c", "d", "e", "f", "g"] +a[4:] # ["e", "f", "g", "h"] +a[-3:] # ["f", "g", "h"] +a[2:5] # ["c", "d", "e"] +a[2:-1] # ["c", "d", "e", "f", "g"] +a[-3:-1] # ["f", "g"] + + +print("Example 6") +first_twenty_items = a[:20] +last_twenty_items = a[-20:] + + +print("Example 7") +try: + a[20] +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +b = a[3:] +print("Before: ", b) +b[1] = 99 +print("After: ", b) +print("No change:", a) + + +print("Example 9") +print("Before ", a) +a[2:7] = [99, 22, 14] +print("After ", a) + + +print("Example 10") +print("Before ", a) +a[2:3] = [47, 11] +print("After ", a) + + +print("Example 11") +b = a[:] +assert b == a and b is not a + + +print("Example 12") +b = a +print("Before a", a) +print("Before b", b) +a[:] = [101, 102, 103] +assert a is b # Still the same list object +print("After a ", a) # Now has different contents +print("After b ", b) # Same list, so same contents as a diff --git a/example_code/item_015.py b/example_code/item_015.py new file mode 100755 index 0000000..0a1bcdb --- /dev/null +++ b/example_code/item_015.py @@ -0,0 +1,106 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +x = ["red", "orange", "yellow", "green", "blue", "purple"] +odds = x[::2] # First, third, fifth +evens = x[1::2] # Second, fourth, sixth +print(odds) +print(evens) + + +print("Example 2") +x = b"mongoose" +y = x[::-1] +print(y) + + +print("Example 3") +x = "寿司" +y = x[::-1] +print(y) + + +print("Example 4") +try: + w = "寿司" + x = w.encode("utf-8") + y = x[::-1] + z = y.decode("utf-8") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +x = ["a", "b", "c", "d", "e", "f", "g", "h"] +x[::2] # ["a", "c", "e", "g"] +x[::-2] # ["h", "f", "d", "b"] +print(x[::2]) +print(x[::-2]) + + +print("Example 6") +x[2::2] # ["c", "e", "g"] +x[-2::-2] # ["g", "e", "c", "a"] +x[-2:2:-2] # ["g", "e"] +x[2:2:-2] # [] +print(x[2::2]) +print(x[-2::-2]) +print(x[-2:2:-2]) +print(x[2:2:-2]) + + +print("Example 7") +y = x[::2] # ["a", "c", "e", "g"] +z = y[1:-1] # ["c", "e"] +print(x) +print(y) +print(z) diff --git a/example_code/item_016.py b/example_code/item_016.py new file mode 100755 index 0000000..bc995ce --- /dev/null +++ b/example_code/item_016.py @@ -0,0 +1,146 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + car_ages = [0, 9, 4, 8, 7, 20, 19, 1, 6, 15] + car_ages_descending = sorted(car_ages, reverse=True) + oldest, second_oldest = car_ages_descending +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +oldest = car_ages_descending[0] +second_oldest = car_ages_descending[1] +others = car_ages_descending[2:] +print(oldest, second_oldest, others) + + +print("Example 3") +oldest, second_oldest, *others = car_ages_descending +print(oldest, second_oldest, others) + + +print("Example 4") +oldest, *others, youngest = car_ages_descending +print(oldest, youngest, others) + +*others, second_youngest, youngest = car_ages_descending +print(youngest, second_youngest, others) + + +print("Example 5") +try: + # This will not compile + source = """*others = car_ages_descending""" + eval(source) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 6") +try: + # This will not compile + source = """first, *middle, *second_middle, last = [1, 2, 3, 4]""" + eval(source) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 7") +car_inventory = { + "Downtown": ("Silver Shadow", "Pinto", "DMC"), + "Airport": ("Skyline", "Viper", "Gremlin", "Nova"), +} +((loc1, (best1, *rest1)), + (loc2, (best2, *rest2))) = car_inventory.items() +print(f"Best at {loc1} is {best1}, {len(rest1)} others") +print(f"Best at {loc2} is {best2}, {len(rest2)} others") + + +print("Example 8") +short_list = [1, 2] +first, second, *rest = short_list +print(first, second, rest) + + +print("Example 9") +it = iter(range(1, 3)) +first, second = it +print(f"{first} and {second}") + + +print("Example 10") +def generate_csv(): + yield ("Date", "Make", "Model", "Year", "Price") + for i in range(100): + yield ("2019-03-25", "Honda", "Fit", "2010", "$3400") + yield ("2019-03-26", "Ford", "F150", "2008", "$2400") + + +print("Example 11") +all_csv_rows = list(generate_csv()) +header = all_csv_rows[0] +rows = all_csv_rows[1:] +print("CSV Header:", header) +print("Row count: ", len(rows)) + + +print("Example 12") +it = generate_csv() +header, *rows = it +print("CSV Header:", header) +print("Row count: ", len(rows)) diff --git a/example_code/item_017.py b/example_code/item_017.py new file mode 100755 index 0000000..700610b --- /dev/null +++ b/example_code/item_017.py @@ -0,0 +1,86 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +from random import randint + +random_bits = 0 +for i in range(32): + if randint(0, 1): + random_bits |= 1 << i + +print(bin(random_bits)) + + +print("Example 2") +flavor_list = ["vanilla", "chocolate", "pecan", "strawberry"] +for flavor in flavor_list: + print(f"{flavor} is delicious") + + +print("Example 3") +for i in range(len(flavor_list)): + flavor = flavor_list[i] + print(f"{i + 1}: {flavor}") + + +print("Example 4") +it = enumerate(flavor_list) +print(next(it)) +print(next(it)) + + +print("Example 5") +for i, flavor in enumerate(flavor_list): + print(f"{i + 1}: {flavor}") + + +print("Example 6") +for i, flavor in enumerate(flavor_list, 1): + print(f"{i}: {flavor}") diff --git a/example_code/item_018.py b/example_code/item_018.py new file mode 100755 index 0000000..dbcec05 --- /dev/null +++ b/example_code/item_018.py @@ -0,0 +1,105 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +names = ["Cecilia", "Lise", "Marie"] +counts = [len(n) for n in names] +print(counts) + + +print("Example 2") +longest_name = None +max_count = 0 + +for i in range(len(names)): + count = counts[i] + if count > max_count: + longest_name = names[i] + max_count = count + +print(longest_name) + + +print("Example 3") +longest_name = None +max_count = 0 + +for i, name in enumerate(names): # Changed + count = counts[i] + if count > max_count: + longest_name = name # Changed + max_count = count +assert longest_name == "Cecilia" + + +print("Example 4") +longest_name = None +max_count = 0 + +for name, count in zip(names, counts): # Changed + if count > max_count: + longest_name = name + max_count = count +assert longest_name == "Cecilia" + + +print("Example 5") +names.append("Rosalind") +for name, count in zip(names, counts): + print(name) + + +print("Example 6") +try: + for name, count in zip(names, counts, strict=True): # Changed + print(name) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_019.py b/example_code/item_019.py new file mode 100755 index 0000000..64b6295 --- /dev/null +++ b/example_code/item_019.py @@ -0,0 +1,114 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +for i in range(3): + print("Loop", i) +else: + print("Else block!") + + +print("Example 2") +for i in range(3): + print("Loop", i) + if i == 1: + break +else: + print("Else block!") + + +print("Example 3") +for x in []: + print("Never runs") +else: + print("For else block!") + + +print("Example 4") +while False: + print("Never runs") +else: + print("While else block!") + + +print("Example 5") +a = 4 +b = 9 + +for i in range(2, min(a, b) + 1): + print("Testing", i) + if a % i == 0 and b % i == 0: + print("Not coprime") + break +else: + print("Coprime") + + +print("Example 6") +def coprime(a, b): + for i in range(2, min(a, b) + 1): + if a % i == 0 and b % i == 0: + return False + return True + +assert coprime(4, 9) +assert not coprime(3, 6) + + +print("Example 7") +def coprime_alternate(a, b): + is_coprime = True + for i in range(2, min(a, b) + 1): + if a % i == 0 and b % i == 0: + is_coprime = False + break + return is_coprime + +assert coprime_alternate(4, 9) +assert not coprime_alternate(3, 6) diff --git a/example_code/item_020.py b/example_code/item_020.py new file mode 100755 index 0000000..39760e3 --- /dev/null +++ b/example_code/item_020.py @@ -0,0 +1,93 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +for i in range(3): + print(f"Inside {i=}") +print(f"After {i=}") + + +print("Example 2") +categories = ["Hydrogen", "Uranium", "Iron", "Other"] +for i, name in enumerate(categories): + if name == "Iron": + break +print(i) + + +print("Example 3") +for i, name in enumerate(categories): + if name == "Lithium": + break +print(i) + + +print("Example 4") +try: + del i + categories = [] + for i, name in enumerate(categories): + if name == "Lithium": + break + print(i) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +try: + my_numbers = [37, 13, 128, 21] + found = [i for i in my_numbers if i % 2 == 0] + print(i) # Always raises +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_021.py b/example_code/item_021.py new file mode 100755 index 0000000..c871012 --- /dev/null +++ b/example_code/item_021.py @@ -0,0 +1,213 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def normalize(numbers): + total = sum(numbers) + result = [] + for value in numbers: + percent = 100 * value / total + result.append(percent) + return result + + +print("Example 2") +visits = [15, 35, 80] +percentages = normalize(visits) +print(percentages) +assert sum(percentages) == 100.0 + + +print("Example 3") +path = "my_numbers.txt" +with open(path, "w") as f: + for i in (15, 35, 80): + f.write(f"{i}\n") + +def read_visits(data_path): + with open(data_path) as f: + for line in f: + yield int(line) + + +print("Example 4") +it = read_visits("my_numbers.txt") +percentages = normalize(it) +print(percentages) + + +print("Example 5") +it = read_visits("my_numbers.txt") +print(list(it)) +print(list(it)) # Already exhausted + + +print("Example 6") +def normalize_copy(numbers): + numbers_copy = list(numbers) # Copy the iterator + total = sum(numbers_copy) + result = [] + for value in numbers_copy: + percent = 100 * value / total + result.append(percent) + return result + + +print("Example 7") +it = read_visits("my_numbers.txt") +percentages = normalize_copy(it) +print(percentages) +assert sum(percentages) == 100.0 + + +print("Example 8") +def normalize_func(get_iter): + total = sum(get_iter()) # New iterator + result = [] + for value in get_iter(): # New iterator + percent = 100 * value / total + result.append(percent) + return result + + +print("Example 9") +path = "my_numbers.txt" +percentages = normalize_func(lambda: read_visits(path)) +print(percentages) +assert sum(percentages) == 100.0 + + +print("Example 10") +class ReadVisits: + def __init__(self, data_path): + self.data_path = data_path + + def __iter__(self): + with open(self.data_path) as f: + for line in f: + yield int(line) + + +print("Example 11") +visits = ReadVisits(path) +percentages = normalize(visits) # Changed +print(percentages) +assert sum(percentages) == 100.0 + + +print("Example 12") +def normalize_defensive(numbers): + if iter(numbers) is numbers: # An iterator -- bad! + raise TypeError("Must supply a container") + total = sum(numbers) + result = [] + for value in numbers: + percent = 100 * value / total + result.append(percent) + return result + + +visits = [15, 35, 80] +normalize_defensive(visits) # No error + +it = iter(visits) +try: + normalize_defensive(it) +except TypeError: + pass +else: + assert False + + +print("Example 13") +from collections.abc import Iterator + +def normalize_defensive(numbers): + if isinstance(numbers, Iterator): # Another way to check + raise TypeError("Must supply a container") + total = sum(numbers) + result = [] + for value in numbers: + percent = 100 * value / total + result.append(percent) + return result + + +visits = [15, 35, 80] +normalize_defensive(visits) # No error + +it = iter(visits) +try: + normalize_defensive(it) +except TypeError: + pass +else: + assert False + + +print("Example 14") +visits_list = [15, 35, 80] +list_percentages = normalize_defensive(visits_list) + +visits_obj = ReadVisits(path) +obj_percentages = normalize_defensive(visits_obj) + +assert list_percentages == obj_percentages +assert sum(percentages) == 100.0 + + +print("Example 15") +try: + visits = [15, 35, 80] + it = iter(visits) + normalize_defensive(it) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_022.py b/example_code/item_022.py new file mode 100755 index 0000000..7147f02 --- /dev/null +++ b/example_code/item_022.py @@ -0,0 +1,217 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + search_key = "red" + my_dict = {"red": 1, "blue": 2, "green": 3} + + for key in my_dict: + if key == "blue": + my_dict["yellow"] = 4 # Causes error +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +try: + my_dict = {"red": 1, "blue": 2, "green": 3} + for key in my_dict: + if key == "blue": + del my_dict["green"] # Causes error +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +my_dict = {"red": 1, "blue": 2, "green": 3} +for key in my_dict: + if key == "blue": + my_dict["green"] = 4 # Okay +print(my_dict) + + +print("Example 4") +try: + my_set = {"red", "blue", "green"} + + for color in my_set: + if color == "blue": + my_set.add("yellow") # Causes error +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +my_set = {"red", "blue", "green"} +for color in my_set: + if color == "blue": + my_set.add("green") # Okay + +print(my_set) + + +print("Example 6") +my_list = [1, 2, 3] + +for number in my_list: + print(number) + if number == 2: + my_list[0] = -1 # Okay + +print(my_list) + + +print("Example 7") +my_list = [1, 2, 3] +bad_count = 0 + +for number in my_list: + print(number) + if number == 2: + my_list.insert(0, 4) # Causes error + # Break out of the infinite loop + bad_count += 1 + if bad_count > 5: + print("...") + break + + +print("Example 8") +my_list = [1, 2, 3] + +for number in my_list: + print(number) + if number == 2: + my_list.append(4) # Okay this time + +print(my_list) + + +print("Example 9") +my_dict = {"red": 1, "blue": 2, "green": 3} + +keys_copy = list(my_dict.keys()) # Copy +for key in keys_copy: # Iterate over copy + if key == "blue": + my_dict["green"] = 4 # Modify original dict + +print(my_dict) + + +print("Example 10") +my_list = [1, 2, 3] + +list_copy = list(my_list) # Copy +for number in list_copy: # Iterate over copy + print(number) + if number == 2: + my_list.insert(0, 4) # Inserts in original list + +print(my_list) + + +print("Example 11") +my_set = {"red", "blue", "green"} + +set_copy = set(my_set) # Copy +for color in set_copy: # Iterate over copy + if color == "blue": + my_set.add("yellow") # Add to original set + +print(my_set) + + +print("Example 12") +my_dict = {"red": 1, "blue": 2, "green": 3} +modifications = {} + +for key in my_dict: + if key == "blue": + modifications["green"] = 4 # Add to staging + +my_dict.update(modifications) # Merge modifications +print(my_dict) + + +print("Example 13") +my_dict = {"red": 1, "blue": 2, "green": 3} +modifications = {} + +for key in my_dict: + if key == "blue": + modifications["green"] = 4 + value = my_dict[key] + if value == 4: # This condition is never true + modifications["yellow"] = 5 + +my_dict.update(modifications) # Merge modifications +print(my_dict) + + +print("Example 14") +my_dict = {"red": 1, "blue": 2, "green": 3} +modifications = {} + +for key in my_dict: + if key == "blue": + modifications["green"] = 4 + value = my_dict[key] + other_value = modifications.get(key) # Check cache + if value == 4 or other_value == 4: + modifications["yellow"] = 5 + +my_dict.update(modifications) # Merge modifications +print(my_dict) diff --git a/example_code/item_023.py b/example_code/item_023.py new file mode 100755 index 0000000..39ef183 --- /dev/null +++ b/example_code/item_023.py @@ -0,0 +1,140 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import random + +def flip_coin(): + if random.randint(0, 1) == 0: + return "Heads" + else: + return "Tails" + +def flip_is_heads(): + return flip_coin() == "Heads" + + +print("Example 2") +flips = [flip_is_heads() for _ in range(20)] +all_heads = False not in flips +assert not all_heads # Very unlikely to be True + + +print("Example 3") +all_heads = True +for _ in range(100): + if not flip_is_heads(): + all_heads = False + break +assert not all_heads # Very unlikely to be True + + +print("Example 4") +print("All truthy:") +print(all([1, 2, 3])) +print(1 and 2 and 3) + +print("One falsey:") +print(all([1, 0, 3])) +print(1 and 0 and 3) + + +print("Example 5") +all_heads = all(flip_is_heads() for _ in range(20)) +assert not all_heads + + +print("Example 6") +all_heads = all([flip_is_heads() for _ in range(20)]) # Wrong +assert not all_heads + + +print("Example 7") +def repeated_is_heads(count): + for _ in range(count): + yield flip_is_heads() # Generator + +all_heads = all(repeated_is_heads(20)) +assert not all_heads + + +print("Example 8") +def flip_is_tails(): + return flip_coin() == "Tails" + + +print("Example 9") +print("All falsey:") +print(any([0, False, None])) +print(0 or False or None) + +print("One truthy:") +print(any([None, 3, 0])) +print(None or 3 or 0) + + +print("Example 10") +all_heads = not any(flip_is_tails() for _ in range(20)) +assert not all_heads + + +print("Example 11") +def repeated_is_tails(count): + for _ in range(count): + yield flip_is_tails() + +all_heads = not any(repeated_is_tails(20)) +assert not all_heads + + +print("Example 12") +for a in (True, False): + for b in (True, False): + assert any([a, b]) == (not all([not a, not b])) + assert all([a, b]) == (not any([not a, not b])) diff --git a/example_code/item_024.py b/example_code/item_024.py new file mode 100755 index 0000000..bafaab7 --- /dev/null +++ b/example_code/item_024.py @@ -0,0 +1,187 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import itertools + + +print("Example 2") +it = itertools.chain([1, 2, 3], [4, 5, 6]) +print(list(it)) + + +print("Example 3") +it1 = [i * 3 for i in ("a", "b", "c")] +it2 = [j * 2 for j in ("x", "y", "z")] +nested_it = [it1, it2] +output_it = itertools.chain.from_iterable(nested_it) +print(list(output_it)) + + +print("Example 4") +it = itertools.repeat("hello", 3) +print(list(it)) + + +print("Example 5") +it = itertools.cycle([1, 2]) +result = [next(it) for _ in range(10)] +print(result) + + +print("Example 6") +it1, it2, it3 = itertools.tee(["first", "second"], 3) +print(list(it1)) +print(list(it2)) +print(list(it3)) + + +print("Example 7") +keys = ["one", "two", "three"] +values = [1, 2] + +normal = list(zip(keys, values)) +print("zip: ", normal) + +it = itertools.zip_longest(keys, values, fillvalue="nope") +longest = list(it) +print("zip_longest:", longest) + + +print("Example 8") +values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +first_five = itertools.islice(values, 5) +print("First five: ", list(first_five)) + +middle_odds = itertools.islice(values, 2, 8, 2) +print("Middle odds:", list(middle_odds)) + + +print("Example 9") +values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +less_than_seven = lambda x: x < 7 +it = itertools.takewhile(less_than_seven, values) +print(list(it)) + + +print("Example 10") +values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +less_than_seven = lambda x: x < 7 +it = itertools.dropwhile(less_than_seven, values) +print(list(it)) + + +print("Example 11") +values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +evens = lambda x: x % 2 == 0 + +filter_result = filter(evens, values) +print("Filter: ", list(filter_result)) + +filter_false_result = itertools.filterfalse(evens, values) +print("Filter false:", list(filter_false_result)) + + +print("Example 12") +it = itertools.batched([1, 2, 3, 4, 5, 6, 7, 8, 9], 3) +print(list(it)) + + +print("Example 13") +it = itertools.batched([1, 2, 3], 2) +print(list(it)) + + +print("Example 14") +route = ["Los Angeles", "Bakersfield", "Modesto", "Sacramento"] +it = itertools.pairwise(route) +print(list(it)) + + +print("Example 15") +values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +sum_reduce = itertools.accumulate(values) +print("Sum: ", list(sum_reduce)) + +def sum_modulo_20(first, second): + output = first + second + return output % 20 + +modulo_reduce = itertools.accumulate(values, sum_modulo_20) +print("Modulo:", list(modulo_reduce)) + + +print("Example 16") +single = itertools.product([1, 2], repeat=2) +print("Single: ", list(single)) + +multiple = itertools.product([1, 2], ["a", "b"]) +print("Multiple:", list(multiple)) + + +print("Example 17") +it = itertools.permutations([1, 2, 3, 4], 2) +original_print = print +print = pprint +print(list(it)) +print = original_print + + +print("Example 18") +it = itertools.combinations([1, 2, 3, 4], 2) +print(list(it)) + + +print("Example 19") +it = itertools.combinations_with_replacement([1, 2, 3, 4], 2) +original_print = print +print = pprint +print(list(it)) +print = original_print diff --git a/example_code/item_025.py b/example_code/item_025.py new file mode 100755 index 0000000..9462683 --- /dev/null +++ b/example_code/item_025.py @@ -0,0 +1,189 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 2") +baby_names = { + "cat": "kitten", + "dog": "puppy", +} +print(baby_names) + + +print("Example 4") +print(list(baby_names.keys())) +print(list(baby_names.values())) +print(list(baby_names.items())) +print(baby_names.popitem()) # Last item inserted + + +print("Example 6") +def my_func(**kwargs): + for key, value in kwargs.items(): + print(f"{key} = {value}") + +my_func(goose="gosling", kangaroo="joey") + + +print("Example 8") +class MyClass: + def __init__(self): + self.alligator = "hatchling" + self.elephant = "calf" + +a = MyClass() +for key, value in a.__dict__.items(): + print(f"{key} = {value}") + + +print("Example 9") +votes = { + "otter": 1281, + "polar bear": 587, + "fox": 863, +} + + +print("Example 10") +def populate_ranks(votes, ranks): + names = list(votes.keys()) + names.sort(key=votes.get, reverse=True) + for i, name in enumerate(names, 1): + ranks[name] = i + + +print("Example 11") +def get_winner(ranks): + return next(iter(ranks)) + + +print("Example 12") +ranks = {} +populate_ranks(votes, ranks) +print(ranks) +winner = get_winner(ranks) +print(winner) + + +print("Example 13") +from collections.abc import MutableMapping + +class SortedDict(MutableMapping): + def __init__(self): + self.data = {} + + def __getitem__(self, key): + return self.data[key] + + def __setitem__(self, key, value): + self.data[key] = value + + def __delitem__(self, key): + del self.data[key] + + def __iter__(self): + keys = list(self.data.keys()) + keys.sort() + for key in keys: + yield key + + def __len__(self): + return len(self.data) + + +my_dict = SortedDict() +my_dict["otter"] = 1 +my_dict["cheeta"] = 2 +my_dict["anteater"] = 3 +my_dict["deer"] = 4 + +assert my_dict["otter"] == 1 + +assert "cheeta" in my_dict +del my_dict["cheeta"] +assert "cheeta" not in my_dict + +expected = [("anteater", 3), ("deer", 4), ("otter", 1)] +assert list(my_dict.items()) == expected + +assert not isinstance(my_dict, dict) + + +print("Example 14") +sorted_ranks = SortedDict() +populate_ranks(votes, sorted_ranks) +print(sorted_ranks.data) +winner = get_winner(sorted_ranks) +print(winner) + + +print("Example 15") +def get_winner(ranks): + for name, rank in ranks.items(): + if rank == 1: + return name + +winner = get_winner(sorted_ranks) +print(winner) + + +print("Example 16") +try: + def get_winner(ranks): + if not isinstance(ranks, dict): + raise TypeError("must provide a dict instance") + return next(iter(ranks)) + + + assert get_winner(ranks) == "otter" + + get_winner(sorted_ranks) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_025_example_01.py b/example_code/item_025_example_01.py new file mode 100755 index 0000000..8c83fcb --- /dev/null +++ b/example_code/item_025_example_01.py @@ -0,0 +1,25 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3.5 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 1") +# Python 3.5 +baby_names = { + "cat": "kitten", + "dog": "puppy", +} +print(baby_names) diff --git a/example_code/item_025_example_03.py b/example_code/item_025_example_03.py new file mode 100755 index 0000000..7b89df6 --- /dev/null +++ b/example_code/item_025_example_03.py @@ -0,0 +1,28 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3.5 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 3") +# Python 3.5 +baby_names = { + "cat": "kitten", + "dog": "puppy", +} +print(list(baby_names.keys())) +print(list(baby_names.values())) +print(list(baby_names.items())) +print(baby_names.popitem()) # Randomly chooses an item diff --git a/example_code/item_025_example_05.py b/example_code/item_025_example_05.py new file mode 100755 index 0000000..ed7a41c --- /dev/null +++ b/example_code/item_025_example_05.py @@ -0,0 +1,25 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3.5 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 5") +# Python 3.5 +def my_func(**kwargs): + for key, value in kwargs.items(): + print("%s = %s" % (key, value)) + +my_func(goose="gosling", kangaroo="joey") diff --git a/example_code/item_025_example_07.py b/example_code/item_025_example_07.py new file mode 100755 index 0000000..2b8467d --- /dev/null +++ b/example_code/item_025_example_07.py @@ -0,0 +1,28 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3.5 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 7") +# Python 3.5 +class MyClass: + def __init__(self): + self.alligator = "hatchling" + self.elephant = "calf" + +a = MyClass() +for key, value in a.__dict__.items(): + print("%s = %s" % (key, value)) diff --git a/example_code/item_025_example_17.py b/example_code/item_025_example_17.py new file mode 100755 index 0000000..9cdb383 --- /dev/null +++ b/example_code/item_025_example_17.py @@ -0,0 +1,68 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 17") +# Check types in this file with: python -m mypy + +from typing import Dict, MutableMapping + +def populate_ranks(votes: Dict[str, int], ranks: Dict[str, int]) -> None: + names = list(votes.keys()) + names.sort(key=votes.__getitem__, reverse=True) + for i, name in enumerate(names, 1): + ranks[name] = i + +def get_winner(ranks: Dict[str, int]) -> str: + return next(iter(ranks)) + +from typing import Iterator, MutableMapping + +class SortedDict(MutableMapping[str, int]): + def __init__(self) -> None: + self.data: Dict[str, int] = {} + + def __getitem__(self, key: str) -> int: + return self.data[key] + + def __setitem__(self, key: str, value: int) -> None: + self.data[key] = value + + def __delitem__(self, key: str) -> None: + del self.data[key] + + def __iter__(self) -> Iterator[str]: + keys = list(self.data.keys()) + keys.sort() + for key in keys: + yield key + + def __len__(self) -> int: + return len(self.data) + + +votes = { + "otter": 1281, + "polar bear": 587, + "fox": 863, +} + +sorted_ranks = SortedDict() +populate_ranks(votes, sorted_ranks) +print(sorted_ranks.data) +winner = get_winner(sorted_ranks) +print(winner) diff --git a/example_code/item_026.py b/example_code/item_026.py new file mode 100755 index 0000000..c6c58d8 --- /dev/null +++ b/example_code/item_026.py @@ -0,0 +1,198 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +counters = { + "pumpernickel": 2, + "sourdough": 1, +} + + +print("Example 2") +key = "wheat" + +if key in counters: + count = counters[key] +else: + count = 0 + +counters[key] = count + 1 +print(counters) + + +print("Example 3") +key = "brioche" + +try: + count = counters[key] +except KeyError: + count = 0 + +counters[key] = count + 1 + +print(counters) + + +print("Example 4") +key = "multigrain" + +count = counters.get(key, 0) +counters[key] = count + 1 + +print(counters) + + +print("Example 5") +key = "baguette" + +if key not in counters: + counters[key] = 0 +counters[key] += 1 + +key = "ciabatta" + +if key in counters: + counters[key] += 1 +else: + counters[key] = 1 + +key = "ciabatta" + +try: + counters[key] += 1 +except KeyError: + counters[key] = 1 + +print(counters) + + +print("Example 6") +votes = { + "baguette": ["Bob", "Alice"], + "ciabatta": ["Coco", "Deb"], +} + +key = "brioche" +who = "Elmer" + +if key in votes: + names = votes[key] +else: + votes[key] = names = [] + +names.append(who) +print(votes) + + +print("Example 7") +key = "rye" +who = "Felix" + +try: + names = votes[key] +except KeyError: + votes[key] = names = [] + +names.append(who) + +print(votes) + + +print("Example 8") +key = "wheat" +who = "Gertrude" + +names = votes.get(key) +if names is None: + votes[key] = names = [] + +names.append(who) + +print(votes) + + +print("Example 9") +key = "brioche" +who = "Hugh" + +if (names := votes.get(key)) is None: + votes[key] = names = [] + +names.append(who) + +print(votes) + + +print("Example 10") +key = "cornbread" +who = "Kirk" + +names = votes.setdefault(key, []) +names.append(who) + +print(votes) + + +print("Example 11") +data = {} +key = "foo" +value = [] +data.setdefault(key, value) +print("Before:", data) +value.append("hello") +print("After: ", data) + + +print("Example 12") +key = "dutch crunch" + +count = counters.setdefault(key, 0) +counters[key] = count + 1 + +print(counters) diff --git a/example_code/item_027.py b/example_code/item_027.py new file mode 100755 index 0000000..9c5732b --- /dev/null +++ b/example_code/item_027.py @@ -0,0 +1,102 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +visits = { + "Mexico": {"Tulum", "Puerto Vallarta"}, + "Japan": {"Hakone"}, +} + + +print("Example 2") +# Short +visits.setdefault("France", set()).add("Arles") + +# Long +if (japan := visits.get("Japan")) is None: + visits["Japan"] = japan = set() + +japan.add("Kyoto") +original_print = print +print = pprint +print(visits) +print = original_print + + +print("Example 3") +class Visits: + def __init__(self): + self.data = {} + + def add(self, country, city): + city_set = self.data.setdefault(country, set()) + city_set.add(city) + + +print("Example 4") +visits = Visits() +visits.add("Russia", "Yekaterinburg") +visits.add("Tanzania", "Zanzibar") +print(visits.data) + + +print("Example 5") +from collections import defaultdict + +class Visits: + def __init__(self): + self.data = defaultdict(set) + + def add(self, country, city): + self.data[country].add(city) + +visits = Visits() +visits.add("England", "Bath") +visits.add("England", "London") +print(visits.data) diff --git a/example_code/item_028.py b/example_code/item_028.py new file mode 100755 index 0000000..11903ed --- /dev/null +++ b/example_code/item_028.py @@ -0,0 +1,192 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +pictures = {} +path = "profile_1234.png" + +with open(path, "wb") as f: + f.write(b"image data here 1234") + +if (handle := pictures.get(path)) is None: + try: + handle = open(path, "a+b") + except OSError: + print(f"Failed to open path {path}") + raise + else: + pictures[path] = handle + +handle.seek(0) +image_data = handle.read() + +print(pictures) +print(image_data) + + +print("Example 2") +# Examples using in and KeyError +pictures = {} +path = "profile_9991.png" + +with open(path, "wb") as f: + f.write(b"image data here 9991") + +if path in pictures: + handle = pictures[path] +else: + try: + handle = open(path, "a+b") + except OSError: + print(f"Failed to open path {path}") + raise + else: + pictures[path] = handle + +handle.seek(0) +image_data = handle.read() + +print(pictures) +print(image_data) + +pictures = {} +path = "profile_9922.png" + +with open(path, "wb") as f: + f.write(b"image data here 9991") + +try: + handle = pictures[path] +except KeyError: + try: + handle = open(path, "a+b") + except OSError: + print(f"Failed to open path {path}") + raise + else: + pictures[path] = handle + +handle.seek(0) +image_data = handle.read() + +print(pictures) +print(image_data) + + +print("Example 3") +pictures = {} +path = "profile_9239.png" + +with open(path, "wb") as f: + f.write(b"image data here 9239") + +try: + handle = pictures.setdefault(path, open(path, "a+b")) +except OSError: + print(f"Failed to open path {path}") + raise +else: + handle.seek(0) + image_data = handle.read() + +print(pictures) +print(image_data) + + +print("Example 4") +try: + path = "profile_4555.csv" + + with open(path, "wb") as f: + f.write(b"image data here 9239") + + from collections import defaultdict + + def open_picture(profile_path): + try: + return open(profile_path, "a+b") + except OSError: + print(f"Failed to open path {profile_path}") + raise + + pictures = defaultdict(open_picture) + handle = pictures[path] + handle.seek(0) + image_data = handle.read() +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +path = "account_9090.csv" + +with open(path, "wb") as f: + f.write(b"image data here 9090") + +def open_picture(profile_path): + try: + return open(profile_path, "a+b") + except OSError: + print(f"Failed to open path {profile_path}") + raise + +class Pictures(dict): + def __missing__(self, key): + value = open_picture(key) + self[key] = value + return value + +pictures = Pictures() +handle = pictures[path] +handle.seek(0) +image_data = handle.read() +print(pictures) +print(image_data) diff --git a/example_code/item_029.py b/example_code/item_029.py new file mode 100755 index 0000000..ca593cd --- /dev/null +++ b/example_code/item_029.py @@ -0,0 +1,230 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class SimpleGradebook: + def __init__(self): + self._grades = {} + + def add_student(self, name): + self._grades[name] = [] + + def report_grade(self, name, score): + self._grades[name].append(score) + + def average_grade(self, name): + grades = self._grades[name] + return sum(grades) / len(grades) + + +print("Example 2") +book = SimpleGradebook() +book.add_student("Isaac Newton") +book.report_grade("Isaac Newton", 90) +book.report_grade("Isaac Newton", 95) +book.report_grade("Isaac Newton", 85) + +print(book.average_grade("Isaac Newton")) + + +print("Example 3") +from collections import defaultdict + +class BySubjectGradebook: + def __init__(self): + self._grades = {} # Outer dict + + def add_student(self, name): + self._grades[name] = defaultdict(list) # Inner dict + + def report_grade(self, name, subject, grade): + by_subject = self._grades[name] + grade_list = by_subject[subject] + grade_list.append(grade) + + def average_grade(self, name): + by_subject = self._grades[name] + total, count = 0, 0 + for grades in by_subject.values(): + total += sum(grades) + count += len(grades) + return total / count + + +print("Example 4") +book = BySubjectGradebook() +book.add_student("Albert Einstein") +book.report_grade("Albert Einstein", "Math", 75) +book.report_grade("Albert Einstein", "Math", 65) +book.report_grade("Albert Einstein", "Gym", 90) +book.report_grade("Albert Einstein", "Gym", 95) +print(book.average_grade("Albert Einstein")) + + +print("Example 5") +class WeightedGradebook: + def __init__(self): + self._grades = {} + + def add_student(self, name): + self._grades[name] = defaultdict(list) + + def report_grade(self, name, subject, score, weight): + by_subject = self._grades[name] + grade_list = by_subject[subject] + grade_list.append((score, weight)) # Changed + + def average_grade(self, name): + by_subject = self._grades[name] + + score_sum, score_count = 0, 0 + for scores in by_subject.values(): + subject_avg, total_weight = 0, 0 + for score, weight in scores: # Added inner loop + subject_avg += score * weight + total_weight += weight + + score_sum += subject_avg / total_weight + score_count += 1 + + return score_sum / score_count + + +print("Example 6") +book = WeightedGradebook() +book.add_student("Albert Einstein") +book.report_grade("Albert Einstein", "Math", 75, 0.05) +book.report_grade("Albert Einstein", "Math", 65, 0.15) +book.report_grade("Albert Einstein", "Math", 70, 0.80) +book.report_grade("Albert Einstein", "Gym", 100, 0.40) +book.report_grade("Albert Einstein", "Gym", 85, 0.60) +print(book.average_grade("Albert Einstein")) + + +print("Example 7") +grades = [] +grades.append((95, 0.45)) +grades.append((85, 0.55)) +total = sum(score * weight for score, weight in grades) +total_weight = sum(weight for _, weight in grades) +average_grade = total / total_weight +print(average_grade) + + +print("Example 8") +grades = [] +grades.append((95, 0.45, "Great job")) +grades.append((85, 0.55, "Better next time")) +total = sum(score * weight for score, weight, _ in grades) +total_weight = sum(weight for _, weight, _ in grades) +average_grade = total / total_weight +print(average_grade) + + +print("Example 9") +from dataclasses import dataclass + +@dataclass(frozen=True) +class Grade: + score: int + weight: float + + +print("Example 10") +class Subject: + def __init__(self): + self._grades = [] + + def report_grade(self, score, weight): + self._grades.append(Grade(score, weight)) + + def average_grade(self): + total, total_weight = 0, 0 + for grade in self._grades: + total += grade.score * grade.weight + total_weight += grade.weight + return total / total_weight + + +print("Example 11") +class Student: + def __init__(self): + self._subjects = defaultdict(Subject) + + def get_subject(self, name): + return self._subjects[name] + + def average_grade(self): + total, count = 0, 0 + for subject in self._subjects.values(): + total += subject.average_grade() + count += 1 + return total / count + + +print("Example 12") +class Gradebook: + def __init__(self): + self._students = defaultdict(Student) + + def get_student(self, name): + return self._students[name] + + +print("Example 13") +book = Gradebook() +albert = book.get_student("Albert Einstein") +math = albert.get_subject("Math") +math.report_grade(75, 0.05) +math.report_grade(65, 0.15) +math.report_grade(70, 0.80) +gym = albert.get_subject("Gym") +gym.report_grade(100, 0.40) +gym.report_grade(85, 0.60) +print(albert.average_grade()) diff --git a/example_code/item_030.py b/example_code/item_030.py new file mode 100755 index 0000000..3831909 --- /dev/null +++ b/example_code/item_030.py @@ -0,0 +1,99 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def my_func(items): + items.append(4) + +x = [1, 2, 3] +my_func(x) +print(x) # 4 is now in the list + + +print("Example 2") +a = [7, 6, 5] +b = a # Creates an alias +my_func(b) +print(a) # 4 is now in the list + + +print("Example 3") +def capitalize_items(items): + for i in range(len(items)): + items[i] = items[i].capitalize() + +my_items = ["hello", "world"] +items_copy = my_items[:] # Creates a copy +capitalize_items(items_copy) +print(items_copy) + + +print("Example 4") +def concat_pairs(items): + for key in items: + items[key] = f"{key}={items[key]}" + +my_pairs = {"foo": 1, "bar": 2} +pairs_copy = my_pairs.copy() # Creates a copy +concat_pairs(pairs_copy) +print(pairs_copy) + + +print("Example 5") +class MyClass: + def __init__(self, value): + self.value = value + +x = MyClass(10) + +def my_func(obj): + obj.value = 20 # Modifies the object + +my_func(x) +print(x.value) diff --git a/example_code/item_031.py b/example_code/item_031.py new file mode 100755 index 0000000..1535b70 --- /dev/null +++ b/example_code/item_031.py @@ -0,0 +1,181 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def get_stats(numbers): + minimum = min(numbers) + maximum = max(numbers) + return minimum, maximum + +lengths = [63, 73, 72, 60, 67, 66, 71, 61, 72, 70] + +minimum, maximum = get_stats(lengths) # Two return values + +print(f"Min: {minimum}, Max: {maximum}") + + +print("Example 2") +first, second = 1, 2 +assert first == 1 +assert second == 2 + +def my_function(): + return 1, 2 + +first, second = my_function() +assert first == 1 +assert second == 2 + + +print("Example 3") +def get_avg_ratio(numbers): + average = sum(numbers) / len(numbers) + scaled = [x / average for x in numbers] + scaled.sort(reverse=True) + return scaled + +longest, *middle, shortest = get_avg_ratio(lengths) + +print(f"Longest: {longest:>4.0%}") +print(f"Shortest: {shortest:>4.0%}") + + +print("Example 4") +def get_median(numbers): + count = len(numbers) + sorted_numbers = sorted(numbers) + middle = count // 2 + if count % 2 == 0: + lower = sorted_numbers[middle - 1] + upper = sorted_numbers[middle] + median = (lower + upper) / 2 + else: + median = sorted_numbers[middle] + return median + +def get_stats_more(numbers): + minimum = min(numbers) + maximum = max(numbers) + count = len(numbers) + average = sum(numbers) / count + median = get_median(numbers) + return minimum, maximum, average, median, count + +minimum, maximum, average, median, count = get_stats_more(lengths) + +print(f"Min: {minimum}, Max: {maximum}") +print(f"Average: {average}, Median: {median}, Count {count}") + +assert minimum == 60 +assert maximum == 73 +assert average == 67.5 +assert median == 68.5 +assert count == 10 + +# Verify odd count median +_, _, _, median, count = get_stats_more([1, 2, 3]) +assert median == 2 +assert count == 3 + + +print("Example 5") +# Correct: +minimum, maximum, average, median, count = get_stats_more(lengths) + +# Oops! Median and average swapped: +minimum, maximum, median, average, count = get_stats_more(lengths) + + +print("Example 6") +minimum, maximum, average, median, count = get_stats_more( + lengths) + +minimum, maximum, average, median, count = \ + get_stats_more(lengths) + +(minimum, maximum, average, + median, count) = get_stats_more(lengths) + +(minimum, maximum, average, median, count + ) = get_stats_more(lengths) + + +print("Example 7") +from dataclasses import dataclass + +@dataclass +class Stats: + minimum: float + maximum: float + average: float + median: float + count: int + +def get_stats_obj(numbers): + return Stats( + minimum=min(numbers), + maximum=max(numbers), + count=len(numbers), + average=sum(numbers) / count, + median=get_median(numbers), + ) + +result = get_stats_obj(lengths) +print(result) + +assert result.minimum == 60 +assert result.maximum == 73 +assert result.average == 67.5 +assert result.median == 68.5 +assert result.count == 10 + +# Verify odd count median +result2 = get_stats_obj([1, 2, 3]) +assert result2.median == 2 +assert result2.count == 3 diff --git a/example_code/item_032.py b/example_code/item_032.py new file mode 100755 index 0000000..859860e --- /dev/null +++ b/example_code/item_032.py @@ -0,0 +1,125 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def careful_divide(a, b): + try: + return a / b + except ZeroDivisionError: + return None + + +assert careful_divide(4, 2) == 2 +assert careful_divide(0, 1) == 0 +assert careful_divide(3, 6) == 0.5 +assert careful_divide(1, 0) == None + + +print("Example 2") +x, y = 1, 0 +result = careful_divide(x, y) +if result is None: + print("Invalid inputs") +else: + print(f"Result is {result:.1f}") + + +print("Example 3") +x, y = 0, 5 +result = careful_divide(x, y) +if not result: # Changed + print("Invalid inputs") # This runs! But shouldn't +else: + assert False + + +print("Example 4") +def careful_divide(a, b): + try: + return True, a / b + except ZeroDivisionError: + return False, None + + +assert careful_divide(4, 2) == (True, 2) +assert careful_divide(0, 1) == (True, 0) +assert careful_divide(3, 6) == (True, 0.5) +assert careful_divide(1, 0) == (False, None) + + +print("Example 5") +x, y = 5, 0 +success, result = careful_divide(x, y) +if not success: + print("Invalid inputs") + + +print("Example 6") +x, y = 5, 0 +_, result = careful_divide(x, y) +if not result: + print("Invalid inputs") + + +print("Example 7") +def careful_divide(a, b): + try: + return a / b + except ZeroDivisionError: + raise ValueError("Invalid inputs") # Changed + + +print("Example 8") +x, y = 5, 2 +try: + result = careful_divide(x, y) +except ValueError: + print("Invalid inputs") +else: + print(f"Result is {result:.1f}") diff --git a/example_code/item_032_example_09.py b/example_code/item_032_example_09.py new file mode 100755 index 0000000..c1cee75 --- /dev/null +++ b/example_code/item_032_example_09.py @@ -0,0 +1,41 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 9") +# Check types in this file with: python -m mypy + +def careful_divide(a: float, b: float) -> float: + """Divides a by b. + + Raises: + ValueError: When the inputs cannot be divided. + """ + try: + return a / b + except ZeroDivisionError: + raise ValueError("Invalid inputs") + +try: + result = careful_divide(1, 0) +except ValueError: + print("Invalid inputs") # Expected +else: + print(f"Result is {result:.1f}") + + +assert careful_divide(1, 5) == 0.2 diff --git a/example_code/item_033.py b/example_code/item_033.py new file mode 100755 index 0000000..0124f84 --- /dev/null +++ b/example_code/item_033.py @@ -0,0 +1,151 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def sort_priority(values, group): + def helper(x): + if x in group: + return (0, x) + return (1, x) + + values.sort(key=helper) + + +print("Example 2") +numbers = [8, 3, 1, 2, 5, 4, 7, 6] +group = {2, 3, 5, 7} +sort_priority(numbers, group) +print(numbers) + + +print("Example 3") +def sort_priority2(numbers, group): + found = False # Flag initial value + + def helper(x): + if x in group: + found = True # Flip the flag + return (0, x) + return (1, x) + + numbers.sort(key=helper) + return found # Flag final value + + +print("Example 4") +numbers = [8, 3, 1, 2, 5, 4, 7, 6] +found = sort_priority2(numbers, group) +print("Found:", found) +print(numbers) + + +print("Example 5") +try: + foo = does_not_exist * 5 +except: + logging.exception('Expected') +else: + assert False + + +print("Example 6") +def sort_priority2(numbers, group): + found = False # Scope: 'sort_priority2' + + def helper(x): + if x in group: + found = True # Scope: 'helper' -- Bad! + return (0, x) + return (1, x) + + numbers.sort(key=helper) + return found + + +print("Example 7") +def sort_priority3(numbers, group): + found = False + + def helper(x): + nonlocal found # Added + if x in group: + found = True + return (0, x) + return (1, x) + + numbers.sort(key=helper) + return found + + +print("Example 8") +numbers = [8, 3, 1, 2, 5, 4, 7, 6] +found = sort_priority3(numbers, group) +print("Found:", found) +print(numbers) + + +print("Example 9") +class Sorter: + def __init__(self, group): + self.group = group + self.found = False + + def __call__(self, x): + if x in self.group: + self.found = True + return (0, x) + return (1, x) + + +print("Example 10") +numbers = [8, 3, 1, 2, 5, 4, 7, 6] +sorter = Sorter(group) +numbers.sort(key=sorter) +print("Found:", sorter.found) +print(numbers) diff --git a/example_code/item_034.py b/example_code/item_034.py new file mode 100755 index 0000000..1c54553 --- /dev/null +++ b/example_code/item_034.py @@ -0,0 +1,101 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def log(message, values): + if not values: + print(message) + else: + values_str = ", ".join(str(x) for x in values) + print(f"{message}: {values_str}") + +log("My numbers are", [1, 2]) +log("Hi there", []) + + +print("Example 2") +def log(message, *values): # Changed + if not values: + print(message) + else: + values_str = ", ".join(str(x) for x in values) + print(f"{message}: {values_str}") + +log("My numbers are", 1, 2) +log("Hi there") # Changed + + +print("Example 3") +favorites = [7, 33, 99] +log("Favorite colors", *favorites) + + +print("Example 4") +def my_generator(): + for i in range(10): + yield i + +def my_func(*args): + print(args) + +it = my_generator() +my_func(*it) + + +print("Example 5") +def log_seq(sequence, message, *values): + if not values: + print(f"{sequence} - {message}") + else: + values_str = ", ".join(str(x) for x in values) + print(f"{sequence} - {message}: {values_str}") + +log_seq(1, "Favorites", 7, 33) # New with *args OK +log_seq(1, "Hi there") # New message only OK +log_seq("Favorite numbers", 7, 33) # Old usage breaks diff --git a/example_code/item_035.py b/example_code/item_035.py new file mode 100755 index 0000000..0b5ab67 --- /dev/null +++ b/example_code/item_035.py @@ -0,0 +1,169 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def remainder(number, divisor): + return number % divisor + +assert remainder(20, 7) == 6 + + +print("Example 2") +remainder(20, 7) +remainder(20, divisor=7) +remainder(number=20, divisor=7) +remainder(divisor=7, number=20) + + +print("Example 3") +try: + # This will not compile + source = """remainder(number=20, 7)""" + eval(source) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 4") +try: + remainder(20, number=7) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +my_kwargs = { + "number": 20, + "divisor": 7, +} +assert remainder(**my_kwargs) == 6 + + +print("Example 6") +my_kwargs = { + "divisor": 7, +} +assert remainder(number=20, **my_kwargs) == 6 + + +print("Example 7") +my_kwargs = { + "number": 20, +} +other_kwargs = { + "divisor": 7, +} +assert remainder(**my_kwargs, **other_kwargs) == 6 + + +print("Example 8") +def print_parameters(**kwargs): + for key, value in kwargs.items(): + print(f"{key} = {value}") + +print_parameters(alpha=1.5, beta=9, gamma=4) + + +print("Example 9") +def flow_rate(weight_diff, time_diff): + return weight_diff / time_diff + +weight_a = 2.5 +weight_b = 3 +time_a = 1 +time_b = 4 +weight_diff = weight_b - weight_a +time_diff = time_b - time_a +flow = flow_rate(weight_diff, time_diff) +print(f"{flow:.3} kg per second") + + +print("Example 10") +def flow_rate(weight_diff, time_diff, period): + return (weight_diff / time_diff) * period + + +print("Example 11") +flow_per_second = flow_rate(weight_diff, time_diff, 1) + + +print("Example 12") +def flow_rate(weight_diff, time_diff, period=1): # Changed + return (weight_diff / time_diff) * period + + +print("Example 13") +flow_per_second = flow_rate(weight_diff, time_diff) +flow_per_hour = flow_rate(weight_diff, time_diff, period=3600) +print(flow_per_second) +print(flow_per_hour) + + +print("Example 14") +def flow_rate(weight_diff, time_diff, period=1, units_per_kg=1): + return ((weight_diff * units_per_kg) / time_diff) * period + + +print("Example 15") +pounds_per_hour = flow_rate( + weight_diff, + time_diff, + period=3600, + units_per_kg=2.2, +) +print(pounds_per_hour) + + +print("Example 16") +pounds_per_hour = flow_rate(weight_diff, time_diff, 3600, 2.2) +print(pounds_per_hour) diff --git a/example_code/item_036.py b/example_code/item_036.py new file mode 100755 index 0000000..1a0c005 --- /dev/null +++ b/example_code/item_036.py @@ -0,0 +1,129 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +from time import sleep +from datetime import datetime + +def log(message, when=datetime.now()): + print(f"{when}: {message}") + +log("Hi there!") +sleep(0.1) +log("Hello again!") + + +print("Example 2") +def log(message, when=None): + """Log a message with a timestamp. + + Args: + message: Message to print. + when: datetime of when the message occurred. + Defaults to the present time. + """ + if when is None: + when = datetime.now() + print(f"{when}: {message}") + + +print("Example 3") +log("Hi there!") +sleep(0.1) +log("Hello again!") + + +print("Example 4") +import json + +def decode(data, default={}): + try: + return json.loads(data) + except ValueError: + return default + + +print("Example 5") +foo = decode("bad data") +foo["stuff"] = 5 +bar = decode("also bad") +bar["meep"] = 1 +print("Foo:", foo) +print("Bar:", bar) + + +print("Example 6") +assert foo is bar + + +print("Example 7") +def decode(data, default=None): + """Load JSON data from a string. + + Args: + data: JSON data to decode. + default: Value to return if decoding fails. + Defaults to an empty dictionary. + """ + try: + return json.loads(data) + except ValueError: + if default is None: # Check here + default = {} + return default + + +print("Example 8") +foo = decode("bad data") +foo["stuff"] = 5 +bar = decode("also bad") +bar["meep"] = 1 +print("Foo:", foo) +print("Bar:", bar) +assert foo is not bar diff --git a/example_code/item_036_example_09.py b/example_code/item_036_example_09.py new file mode 100755 index 0000000..2e4164d --- /dev/null +++ b/example_code/item_036_example_09.py @@ -0,0 +1,41 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 9") +# Check types in this file with: python -m mypy + +from datetime import datetime +from time import sleep + +def log_typed(message: str, when: datetime | None = None) -> None: + """Log a message with a timestamp. + + Args: + message: Message to print. + when: datetime of when the message occurred. + Defaults to the present time. + """ + if when is None: + when = datetime.now() + print(f"{when}: {message}") + + +log_typed("Hi there!") +sleep(0.1) +log_typed("Hello again!") +log_typed("And one more time", when=datetime.now()) diff --git a/example_code/item_037.py b/example_code/item_037.py new file mode 100755 index 0000000..7da1cb5 --- /dev/null +++ b/example_code/item_037.py @@ -0,0 +1,263 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def safe_division( + number, + divisor, + ignore_overflow, + ignore_zero_division, +): + try: + return number / divisor + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float("inf") + else: + raise + + +print("Example 2") +result = safe_division(1.0, 10**500, True, False) +print(result) + + +print("Example 3") +result = safe_division(1.0, 0, False, True) +print(result) + + +print("Example 4") +def safe_division_b( + number, + divisor, + ignore_overflow=False, # Changed + ignore_zero_division=False, # Changed +): + try: + return number / divisor + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float("inf") + else: + raise + + +print("Example 5") +result = safe_division_b(1.0, 10**500, ignore_overflow=True) +print(result) + +result = safe_division_b(1.0, 0, ignore_zero_division=True) +print(result) + + +print("Example 6") +assert safe_division_b(1.0, 10**500, True, False) == 0 + + +print("Example 7") +def safe_division_c( + number, + divisor, + *, # Added + ignore_overflow=False, + ignore_zero_division=False, +): + try: + return number / divisor + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float("inf") + else: + raise + + +print("Example 8") +try: + safe_division_c(1.0, 10**500, True, False) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 9") +result = safe_division_c(1.0, 0, ignore_zero_division=True) +assert result == float("inf") + +try: + result = safe_division_c(1.0, 0) +except ZeroDivisionError: + pass # Expected +else: + assert False + + +print("Example 10") +assert safe_division_c(number=2, divisor=5) == 0.4 +assert safe_division_c(divisor=5, number=2) == 0.4 +assert safe_division_c(2, divisor=5) == 0.4 + + +print("Example 11") +def safe_division_d( + numerator, # Changed + denominator, # Changed + *, + ignore_overflow=False, + ignore_zero_division=False +): + try: + return numerator / denominator + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float("inf") + else: + raise + + +print("Example 12") +try: + safe_division_d(number=2, divisor=5) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 13") +def safe_division_e( + numerator, + denominator, + /, # Added + *, + ignore_overflow=False, + ignore_zero_division=False, +): + try: + return numerator / denominator + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float("inf") + else: + raise + + +print("Example 14") +assert safe_division_e(2, 5) == 0.4 + + +print("Example 15") +try: + safe_division_e(numerator=2, denominator=5) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 16") +def safe_division_f( + numerator, + denominator, + /, + ndigits=10, # Changed + *, + ignore_overflow=False, + ignore_zero_division=False, +): + try: + fraction = numerator / denominator # Changed + return round(fraction, ndigits) # Changed + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float("inf") + else: + raise + + +print("Example 17") +result = safe_division_f(22, 7) +print(result) + +result = safe_division_f(22, 7, 5) +print(result) + +result = safe_division_f(22, 7, ndigits=2) +print(result) diff --git a/example_code/item_038.py b/example_code/item_038.py new file mode 100755 index 0000000..855930c --- /dev/null +++ b/example_code/item_038.py @@ -0,0 +1,133 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def trace(func): + def wrapper(*args, **kwargs): + args_repr = repr(args) + kwargs_repr = repr(kwargs) + result = func(*args, **kwargs) + print(f"{func.__name__}" + f"({args_repr}, {kwargs_repr}) " + f"-> {result!r}") + return result + + return wrapper + + +print("Example 2") +@trace +def fibonacci(n): + """Return the n-th Fibonacci number""" + if n in (0, 1): + return n + return fibonacci(n - 2) + fibonacci(n - 1) + + +print("Example 3") +def fibonacci(n): + """Return the n-th Fibonacci number""" + if n in (0, 1): + return n + return fibonacci(n - 2) + fibonacci(n - 1) + +fibonacci = trace(fibonacci) + + +print("Example 4") +fibonacci(4) + + +print("Example 5") +print(fibonacci) + + +print("Example 6") +help(fibonacci) + + +print("Example 7") +try: + import pickle + + pickle.dumps(fibonacci) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +from functools import wraps + +def trace(func): + @wraps(func) # Changed + def wrapper(*args, **kwargs): + args_repr = repr(args) + kwargs_repr = repr(kwargs) + result = func(*args, **kwargs) + print(f"{func.__name__}" f"({args_repr}, {kwargs_repr}) " f"-> {result!r}") + return result + + return wrapper + +@trace +def fibonacci(n): + """Return the n-th Fibonacci number""" + if n in (0, 1): + return n + return fibonacci(n - 2) + fibonacci(n - 1) + + +print("Example 9") +help(fibonacci) + + +print("Example 10") +print(pickle.dumps(fibonacci)) diff --git a/example_code/item_039.py b/example_code/item_039.py new file mode 100755 index 0000000..e00eb93 --- /dev/null +++ b/example_code/item_039.py @@ -0,0 +1,132 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import math +import functools + +def log_sum(log_total, value): + log_value = math.log(value) + return log_total + log_value + +result = functools.reduce(log_sum, [10, 20, 40], 0) +print(math.exp(result)) + + +print("Example 2") +def log_sum_alt(value, log_total): # Changed + log_value = math.log(value) + return log_total + log_value + + +print("Example 3") +result = functools.reduce( + lambda total, value: log_sum_alt(value, total), # Reordered + [10, 20, 40], + 0, +) +print(math.exp(result)) + + +print("Example 4") +def log_sum_for_reduce(total, value): + return log_sum_alt(value, total) + +result = functools.reduce( + log_sum_for_reduce, + [10, 20, 40], + 0, +) +print(math.exp(result)) + + +print("Example 5") +def logn_sum(base, logn_total, value): # New first parameter + logn_value = math.log(value, base) + return logn_total + logn_value + + +print("Example 6") +result = functools.reduce( + lambda total, value: logn_sum(10, total, value), # Changed + [10, 20, 40], + 0, +) +print(math.pow(10, result)) + + +print("Example 7") +result = functools.reduce( + functools.partial(logn_sum, 10), # Changed + [10, 20, 40], + 0, +) +print(math.pow(10, result)) + + +print("Example 8") +def logn_sum_last(logn_total, value, *, base=10): # New last parameter + logn_value = math.log(value, base) + return logn_total + logn_value + + +print("Example 9") +import math + +log_sum_e = functools.partial(logn_sum_last, base=math.e) # Pinned `base` +print(log_sum_e(3, math.e**10)) + + +print("Example 10") +log_sum_e_alt = lambda *a, base=math.e, **kw: logn_sum_last(*a, base=base, **kw) +print(log_sum_e_alt(3, math.e**10)) + + +print("Example 11") +print(log_sum_e.args, log_sum_e.keywords, log_sum_e.func) diff --git a/example_code/item_040.py b/example_code/item_040.py new file mode 100755 index 0000000..24015b5 --- /dev/null +++ b/example_code/item_040.py @@ -0,0 +1,99 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +squares = [] +for x in a: + squares.append(x**2) +print(squares) + + +print("Example 2") +squares = [x**2 for x in a] # List comprehension +print(squares) + + +print("Example 3") +alt = map(lambda x: x**2, a) +assert list(alt) == squares, f"{alt} {squares}" + + +print("Example 4") +even_squares = [x**2 for x in a if x % 2 == 0] +print(even_squares) + + +print("Example 5") +alt = map(lambda x: x**2, filter(lambda x: x % 2 == 0, a)) +assert even_squares == list(alt) + + +print("Example 6") +even_squares_dict = {x: x**2 for x in a if x % 2 == 0} +threes_cubed_set = {x**3 for x in a if x % 3 == 0} +print(even_squares_dict) +print(threes_cubed_set) + + +print("Example 7") +alt_dict = dict( + map( + lambda x: (x, x**2), + filter(lambda x: x % 2 == 0, a), + ) +) +alt_set = set( + map( + lambda x: x**3, + filter(lambda x: x % 3 == 0, a), + ) +) +assert even_squares_dict == alt_dict +assert threes_cubed_set == alt_set diff --git a/example_code/item_041.py b/example_code/item_041.py new file mode 100755 index 0000000..8310508 --- /dev/null +++ b/example_code/item_041.py @@ -0,0 +1,102 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +matrix = [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], +] +flat = [x for row in matrix for x in row] +print(flat) + + +print("Example 2") +squared = [[x**2 for x in row] for row in matrix] +print(squared) + + +print("Example 3") +my_lists = [ + [[1, 2, 3], [4, 5, 6]], + [[7, 8, 9], [10, 11, 12]], +] +flat = [x for sublist1 in my_lists + for sublist2 in sublist1 + for x in sublist2] +print(flat) + + +print("Example 4") +flat = [] +for sublist1 in my_lists: + for sublist2 in sublist1: + flat.extend(sublist2) +print(flat) + + +print("Example 5") +a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +b = [x for x in a if x > 4 if x % 2 == 0] +c = [x for x in a if x > 4 and x % 2 == 0] +print(b) +print(c) +assert b and c +assert b == c + + +print("Example 6") +matrix = [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], +] +filtered = [[x for x in row if x % 4 == 0] + for row in matrix if sum(row) >= 10] +print(filtered) diff --git a/example_code/item_042.py b/example_code/item_042.py new file mode 100755 index 0000000..145b428 --- /dev/null +++ b/example_code/item_042.py @@ -0,0 +1,142 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +stock = { + "nails": 125, + "screws": 35, + "wingnuts": 8, + "washers": 24, +} + +order = ["screws", "wingnuts", "clips"] + +def get_batches(count, size): + return count // size + +result = {} +for name in order: + count = stock.get(name, 0) + batches = get_batches(count, 8) + if batches: + result[name] = batches + +print(result) + + +print("Example 2") +found = {name: get_batches(stock.get(name, 0), 8) + for name in order + if get_batches(stock.get(name, 0), 8)} +print(found) + + +print("Example 3") +has_bug = {name: get_batches(stock.get(name, 0), 4) # Wrong + for name in order + if get_batches(stock.get(name, 0), 8)} + +print("Expected:", found) +print("Found: ", has_bug) + + +print("Example 4") +found = {name: batches for name in order + if (batches := get_batches(stock.get(name, 0), 8))} +assert found == {"screws": 4, "wingnuts": 1}, found + + +print("Example 5") +try: + result = {name: (tenth := count // 10) + for name, count in stock.items() if tenth > 0} +except: + logging.exception('Expected') +else: + assert False + + +print("Example 6") +result = {name: tenth for name, count in stock.items() + if (tenth := count // 10) > 0} +print(result) + + +print("Example 7") +half = [(squared := last**2) + for count in stock.values() + if (last := count // 2) > 10] +print(f"Last item of {half} is {last} ** 2 = {squared}") + + +print("Example 8") +for count in stock.values(): + last = count // 2 + squared = last**2 + +print(f"{count} // 2 = {last}; {last} ** 2 = {squared}") + + +print("Example 9") +try: + del count + half = [count // 2 for count in stock.values()] + print(half) # Works + print(count) # Exception because loop variable didn't leak +except: + logging.exception('Expected') +else: + assert False + + +print("Example 10") +found = ((name, batches) for name in order + if (batches := get_batches(stock.get(name, 0), 8))) +print(next(found)) +print(next(found)) diff --git a/example_code/item_043.py b/example_code/item_043.py new file mode 100755 index 0000000..99fab75 --- /dev/null +++ b/example_code/item_043.py @@ -0,0 +1,115 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def index_words(text): + result = [] + if text: + result.append(0) + for index, letter in enumerate(text): + if letter == " ": + result.append(index + 1) + return result + + +print("Example 2") +address = "Four score and seven years ago..." +address = "Four score and seven years ago our fathers brought forth on this continent a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal." +result = index_words(address) +print(result[:10]) + + +print("Example 3") +def index_words_iter(text): + if text: + yield 0 + for index, letter in enumerate(text): + if letter == " ": + yield index + 1 + + +print("Example 4") +it = index_words_iter(address) +print(next(it)) +print(next(it)) + + +print("Example 5") +result = list(index_words_iter(address)) +print(result[:10]) + + +print("Example 6") +def index_file(handle): + offset = 0 + for line in handle: + if line: + yield offset + for letter in line: + offset += 1 + if letter == " ": + yield offset + + +print("Example 7") +address_lines = """Four score and seven years +ago our fathers brought forth on this +continent a new nation, conceived in liberty, +and dedicated to the proposition that all men +are created equal.""" + +with open("address.txt", "w") as f: + f.write(address_lines) + +import itertools + +with open("address.txt", "r") as f: + it = index_file(f) + results = itertools.islice(it, 0, 10) + print(list(results)) diff --git a/example_code/item_044.py b/example_code/item_044.py new file mode 100755 index 0000000..a35ba2d --- /dev/null +++ b/example_code/item_044.py @@ -0,0 +1,77 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import random + +with open("my_file.txt", "w") as f: + for _ in range(10): + f.write("a" * random.randint(0, 100)) + f.write("\n") + +value = [len(x) for x in open("my_file.txt")] +print(value) + + +print("Example 2") +it = (len(x) for x in open("my_file.txt")) +print(it) + + +print("Example 3") +print(next(it)) +print(next(it)) + + +print("Example 4") +roots = ((x, x**0.5) for x in it) + + +print("Example 5") +print(next(roots)) diff --git a/example_code/item_045.py b/example_code/item_045.py new file mode 100755 index 0000000..344fedd --- /dev/null +++ b/example_code/item_045.py @@ -0,0 +1,88 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def move(period, speed): + for _ in range(period): + yield speed + +def pause(delay): + for _ in range(delay): + yield 0 + + +print("Example 2") +def animate(): + for delta in move(4, 5.0): + yield delta + for delta in pause(3): + yield delta + for delta in move(2, 3.0): + yield delta + + +print("Example 3") +def render(delta): + print(f"Delta: {delta:.1f}") + # Move the images onscreen + +def run(func): + for delta in func(): + render(delta) + +run(animate) + + +print("Example 4") +def animate_composed(): + yield from move(4, 5.0) + yield from pause(3) + yield from move(2, 3.0) + +run(animate_composed) diff --git a/example_code/item_046.py b/example_code/item_046.py new file mode 100755 index 0000000..b7a33de --- /dev/null +++ b/example_code/item_046.py @@ -0,0 +1,171 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import math + +def wave(amplitude, steps): + step_size = 2 * math.pi / steps + for step in range(steps): + radians = step * step_size + fraction = math.sin(radians) + output = amplitude * fraction + yield output + + +print("Example 2") +def transmit(output): + if output is None: + print(f"Output is None") + else: + print(f"Output: {output:>5.1f}") + +def run(it): + for output in it: + transmit(output) + +run(wave(3.0, 8)) + + +print("Example 3") +def my_generator(): + received = yield 1 + print(f"{received=}") + +it = my_generator() +output = next(it) # Get first generator output +print(f"{output=}") + +try: + next(it) # Run generator until it exits +except StopIteration: + pass +else: + assert False + + +print("Example 4") +it = my_generator() +output = it.send(None) # Get first generator output +print(f"{output=}") + +try: + it.send("hello!") # Send value into the generator +except StopIteration: + pass +else: + assert False + + +print("Example 5") +def wave_modulating(steps): + step_size = 2 * math.pi / steps + amplitude = yield # Receive initial amplitude + for step in range(steps): + radians = step * step_size + fraction = math.sin(radians) + output = amplitude * fraction + amplitude = yield output # Receive next amplitude + + +print("Example 6") +def run_modulating(it): + amplitudes = [None, 7, 7, 7, 2, 2, 2, 2, 10, 10, 10, 10, 10] + for amplitude in amplitudes: + output = it.send(amplitude) + transmit(output) + +run_modulating(wave_modulating(12)) + + +print("Example 7") +def complex_wave(): + yield from wave(7.0, 3) + yield from wave(2.0, 4) + yield from wave(10.0, 5) + +run(complex_wave()) + + +print("Example 8") +def complex_wave_modulating(): + yield from wave_modulating(3) + yield from wave_modulating(4) + yield from wave_modulating(5) + +run_modulating(complex_wave_modulating()) + + +print("Example 9") +def wave_cascading(amplitude_it, steps): + step_size = 2 * math.pi / steps + for step in range(steps): + radians = step * step_size + fraction = math.sin(radians) + amplitude = next(amplitude_it) # Get next input + output = amplitude * fraction + yield output + + +print("Example 10") +def complex_wave_cascading(amplitude_it): + yield from wave_cascading(amplitude_it, 3) + yield from wave_cascading(amplitude_it, 4) + yield from wave_cascading(amplitude_it, 5) + + +print("Example 11") +def run_cascading(): + amplitudes = [7, 7, 7, 2, 2, 2, 2, 10, 10, 10, 10, 10] + it = complex_wave_cascading(iter(amplitudes)) # Supplies iterator + for amplitude in amplitudes: + output = next(it) + transmit(output) + +run_cascading() diff --git a/example_code/item_047.py b/example_code/item_047.py new file mode 100755 index 0000000..1cc5a1a --- /dev/null +++ b/example_code/item_047.py @@ -0,0 +1,177 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + class MyError(Exception): + pass + + def my_generator(): + yield 1 + yield 2 + yield 3 + + it = my_generator() + print(next(it)) # Yields 1 + print(next(it)) # Yields 2 + print(it.throw(MyError("test error"))) # Raises +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +def my_generator(): + yield 1 + + try: + yield 2 + except MyError: + print("Got MyError!") + else: + yield 3 + + yield 4 + +it = my_generator() +print(next(it)) # Yields 1 +print(next(it)) # Yields 2 +print(it.throw(MyError("test error"))) # Yields 4 + + +print("Example 3") +class Reset(Exception): + pass + +def timer(period): + current = period + while current: + try: + yield current + except Reset: + print("Resetting") + current = period + else: + current -= 1 + + +print("Example 4") +ORIGINAL_RESETS = [ + False, + False, + False, + True, + False, + True, + False, + False, + False, + False, + False, + False, + False, + False, +] +RESETS = ORIGINAL_RESETS[:] + +def check_for_reset(): + # Poll for external event + return RESETS.pop(0) + +def announce(remaining): + print(f"{remaining} ticks remaining") + +def run(): + it = timer(4) + while True: + try: + if check_for_reset(): + current = it.throw(Reset()) + else: + current = next(it) + except StopIteration: + break + else: + announce(current) + +run() + + +print("Example 5") +class Timer: + def __init__(self, period): + self.current = period + self.period = period + + def reset(self): + print("Resetting") + self.current = self.period + + def tick(self): + before = self.current + self.current -= 1 + return before + + def __bool__(self): + return self.current > 0 + + +print("Example 6") +RESETS = ORIGINAL_RESETS[:] + +def run(): + timer = Timer(4) + while timer: + if check_for_reset(): + timer.reset() + + announce(timer.tick()) + +run() diff --git a/example_code/item_048.py b/example_code/item_048.py new file mode 100755 index 0000000..902b8fc --- /dev/null +++ b/example_code/item_048.py @@ -0,0 +1,139 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +names = ["Socrates", "Archimedes", "Plato", "Aristotle"] +names.sort(key=len) +print(names) + + +print("Example 2") +def log_missing(): + print("Key added") + return 0 + + +print("Example 3") +from collections import defaultdict + +current = {"green": 12, "blue": 3} +increments = [ + ("red", 5), + ("blue", 17), + ("orange", 9), +] +result = defaultdict(log_missing, current) +print("Before:", dict(result)) +for key, amount in increments: + result[key] += amount +print("After: ", dict(result)) + + +print("Example 4") +def increment_with_report(current, increments): + added_count = 0 + + def missing(): + nonlocal added_count # Stateful closure + added_count += 1 + return 0 + + result = defaultdict(missing, current) + for key, amount in increments: + result[key] += amount + + return result, added_count + + +print("Example 5") +result, count = increment_with_report(current, increments) +assert count == 2 +print(result) + + +print("Example 6") +class CountMissing: + def __init__(self): + self.added = 0 + + def missing(self): + self.added += 1 + return 0 + + +print("Example 7") +counter = CountMissing() +result = defaultdict(counter.missing, current) # Method ref +for key, amount in increments: + result[key] += amount +assert counter.added == 2 +print(result) + + +print("Example 8") +class BetterCountMissing: + def __init__(self): + self.added = 0 + + def __call__(self): + self.added += 1 + return 0 + +counter = BetterCountMissing() +assert counter() == 0 +assert callable(counter) + + +print("Example 9") +counter = BetterCountMissing() +result = defaultdict(counter, current) # Relies on __call__ +for key, amount in increments: + result[key] += amount +assert counter.added == 2 +print(result) diff --git a/example_code/item_049.py b/example_code/item_049.py new file mode 100755 index 0000000..91aec6c --- /dev/null +++ b/example_code/item_049.py @@ -0,0 +1,203 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class Integer: + def __init__(self, value): + self.value = value + +class Add: + def __init__(self, left, right): + self.left = left + self.right = right + +class Multiply: + def __init__(self, left, right): + self.left = left + self.right = right + + +print("Example 2") +tree = Add( + Integer(2), + Integer(9), +) + + +print("Example 3") +def evaluate(node): + if isinstance(node, Integer): + return node.value + elif isinstance(node, Add): + return evaluate(node.left) + evaluate(node.right) + elif isinstance(node, Multiply): + return evaluate(node.left) * evaluate(node.right) + else: + raise NotImplementedError + + +print("Example 4") +print(evaluate(tree)) + + +print("Example 5") +tree = Multiply( + Add(Integer(3), Integer(5)), + Add(Integer(4), Integer(7)), +) +print(evaluate(tree)) + + +print("Example 6") +class Node: + def evaluate(self): + raise NotImplementedError + + +print("Example 7") +class IntegerNode(Node): + def __init__(self, value): + self.value = value + + def evaluate(self): + return self.value + + +print("Example 8") +class AddNode(Node): + def __init__(self, left, right): + self.left = left + self.right = right + + def evaluate(self): + left = self.left.evaluate() + right = self.right.evaluate() + return left + right + +class MultiplyNode(Node): + def __init__(self, left, right): + self.left = left + self.right = right + + def evaluate(self): + left = self.left.evaluate() + right = self.right.evaluate() + return left * right + + +print("Example 9") +tree = MultiplyNode( + AddNode(IntegerNode(3), IntegerNode(5)), + AddNode(IntegerNode(4), IntegerNode(7)), +) +print(tree.evaluate()) + + +print("Example 10") +class NodeAlt: + def evaluate(self): + raise NotImplementedError + + def pretty(self): + raise NotImplementedError + + +print("Example 11") +class IntegerNodeAlt(NodeAlt): + def __init__(self, value): + self.value = value + + def evaluate(self): + return self.value + + + def pretty(self): + return repr(self.value) + + +print("Example 12") +class AddNodeAlt(NodeAlt): + def __init__(self, left, right): + self.left = left + self.right = right + + def evaluate(self): + left = self.left.evaluate() + right = self.right.evaluate() + return left + right + + + def pretty(self): + left_str = self.left.pretty() + right_str = self.right.pretty() + return f"({left_str} + {right_str})" + +class MultiplyNodeAlt(NodeAlt): + def __init__(self, left, right): + self.left = left + self.right = right + + def evaluate(self): + left = self.left.evaluate() + right = self.right.evaluate() + return left + right + + + def pretty(self): + left_str = self.left.pretty() + right_str = self.right.pretty() + return f"({left_str} * {right_str})" + + +print("Example 13") +tree = MultiplyNodeAlt( + AddNodeAlt(IntegerNodeAlt(3), IntegerNodeAlt(5)), + AddNodeAlt(IntegerNodeAlt(4), IntegerNodeAlt(7)), +) +print(tree.pretty()) diff --git a/example_code/item_050.py b/example_code/item_050.py new file mode 100755 index 0000000..4a806ee --- /dev/null +++ b/example_code/item_050.py @@ -0,0 +1,245 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class NodeAlt: + def evaluate(self): + raise NotImplementedError + + def pretty(self): + raise NotImplementedError + +class IntegerNodeAlt(NodeAlt): + def __init__(self, value): + self.value = value + + def evaluate(self): + return self.value + + def pretty(self): + return repr(self.value) + +class AddNodeAlt(NodeAlt): + def __init__(self, left, right): + self.left = left + self.right = right + + def evaluate(self): + left = self.left.evaluate() + right = self.right.evaluate() + return left + right + + def pretty(self): + left_str = self.left.pretty() + right_str = self.right.pretty() + return f"({left_str} + {right_str})" + + +class MultiplyNodeAlt(NodeAlt): + def __init__(self, left, right): + self.left = left + self.right = right + + def evaluate(self): + left = self.left.evaluate() + right = self.right.evaluate() + return left + right + + def pretty(self): + left_str = self.left.pretty() + right_str = self.right.pretty() + return f"({left_str} * {right_str})" + + +print("Example 2") +tree = MultiplyNodeAlt( + AddNodeAlt(IntegerNodeAlt(3), IntegerNodeAlt(5)), + AddNodeAlt(IntegerNodeAlt(4), IntegerNodeAlt(7)), +) +print(tree.evaluate()) +print(tree.pretty()) + + +print("Example 3") +class NodeAlt2: + def evaluate(self): + raise NotImplementedError + + def pretty(self): + raise NotImplementedError + + def solve(self): + raise NotImplementedError + + def error_check(self): + raise NotImplementedError + + def derivative(self): + raise NotImplementedError + + # And 20 more methods... + + +print("Example 4") +import functools + +@functools.singledispatch +def my_print(value): + raise NotImplementedError + + +print("Example 5") +@my_print.register(int) +def _(value): + print("Integer!", value) + +@my_print.register(float) +def _(value): + print("Float!", value) + + +print("Example 6") +my_print(20) +my_print(1.23) + + +print("Example 7") +@functools.singledispatch +def my_evaluate(node): + raise NotImplementedError + + +print("Example 8") +class Integer: + def __init__(self, value): + self.value = value + +@my_evaluate.register(Integer) +def _(node): + return node.value + + +print("Example 9") +class Add: + def __init__(self, left, right): + self.left = left + self.right = right + +@my_evaluate.register(Add) +def _(node): + left = my_evaluate(node.left) + right = my_evaluate(node.right) + return left + right + +class Multiply: + def __init__(self, left, right): + self.left = left + self.right = right + +@my_evaluate.register(Multiply) +def _(node): + left = my_evaluate(node.left) + right = my_evaluate(node.right) + return left * right + + +print("Example 10") +tree = Multiply( + Add(Integer(3), Integer(5)), + Add(Integer(4), Integer(7)), +) +result = my_evaluate(tree) +print(result) + + +print("Example 11") +@functools.singledispatch +def my_pretty(node): + raise NotImplementedError + +@my_pretty.register(Integer) +def _(node): + return repr(node.value) + +@my_pretty.register(Add) +def _(node): + left_str = my_pretty(node.left) + right_str = my_pretty(node.right) + return f"({left_str} + {right_str})" + +@my_pretty.register(Multiply) +def _(node): + left_str = my_pretty(node.left) + right_str = my_pretty(node.right) + return f"({left_str} * {right_str})" + + +print("Example 12") +print(my_pretty(tree)) + + +print("Example 13") +class PositiveInteger(Integer): + pass + +print(my_pretty(PositiveInteger(1234))) + + +print("Example 14") +try: + class Float: + def __init__(self, value): + self.value = value + + + print(my_pretty(Float(5.678))) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_051.py b/example_code/item_051.py new file mode 100755 index 0000000..5ff2064 --- /dev/null +++ b/example_code/item_051.py @@ -0,0 +1,487 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class RGB: + def __init__(self, red, green, blue): + self.red = red + self.green = green + self.blue = blue + + +print("Example 2") +class BadRGB: + def __init__(self, green, red, blue): # Bad: Order swapped + self.red = red + self.green = green + self.bloe = blue # Bad: Typo + + +print("Example 3") +from dataclasses import dataclass + +@dataclass +class DataclassRGB: + red: int + green: int + blue: int + + +print("Example 6") +from typing import Any + +@dataclass +class DataclassRGB: + red: Any + green: Any + blue: Any + + +print("Example 7") +color1 = RGB(red=1, green=2, blue=3) +color2 = RGB(1, 2, 3) +color3 = RGB(1, 2, blue=3) +print(color1.__dict__) +print(color2.__dict__) +print(color3.__dict__) + + +print("Example 8") +class RGB: + def __init__(self, *, red, green, blue): # Changed + self.red = red + self.green = green + self.blue = blue + + +print("Example 9") +color4 = RGB(red=1, green=2, blue=3) + + +print("Example 10") +try: + RGB(1, 2, 3) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 11") +@dataclass(kw_only=True) +class DataclassRGB: + red: int + green: int + blue: int + + +print("Example 12") +color5 = DataclassRGB(red=1, green=2, blue=3) +print(color5) + + +print("Example 13") +try: + DataclassRGB(1, 2, 3) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 14") +class RGBA: + def __init__(self, *, red, green, blue, alpha=1.0): + self.red = red + self.green = green + self.blue = blue + self.alpha = alpha + + +print("Example 15") +color1 = RGBA(red=1, green=2, blue=3) +print( + color1.red, + color1.green, + color1.blue, + color1.alpha, +) + + +print("Example 16") +@dataclass(kw_only=True) +class DataclassRGBA: + red: int + green: int + blue: int + alpha: int = 1.0 + + +print("Example 17") +color2 = DataclassRGBA(red=1, green=2, blue=3) +print(color2) + + +print("Example 18") +class BadContainer: + def __init__(self, *, value=[]): + self.value = value + +obj1 = BadContainer() +obj2 = BadContainer() +obj1.value.append(1) +print(obj2.value) # Should be empty, but isn't + + +print("Example 19") +class MyContainer: + def __init__(self, *, value=None): + if value is None: + value = [] # Create when not supplied + self.value = value + + +print("Example 20") +obj1 = MyContainer() +obj2 = MyContainer() +obj1.value.append(1) +assert obj1.value == [1] +assert obj2.value == [] + + +print("Example 21") +from dataclasses import field + +@dataclass +class DataclassContainer: + value: list = field(default_factory=list) + + +print("Example 22") +obj1 = DataclassContainer() +obj2 = DataclassContainer() +obj1.value.append(1) +assert obj1.value == [1] +assert obj2.value == [] + + +print("Example 23") +color1 = RGB(red=1, green=2, blue=3) +print(color1) + + +print("Example 24") +class RGB: + def __init__(self, *, red, green, blue): + self.red = red + self.green = green + self.blue = blue + + + def __repr__(self): + return ( + f"{type(self).__module__}" + f".{type(self).__name__}(" + f"red={self.red!r}, " + f"green={self.green!r}, " + f"blue={self.blue!r})" + ) + + +print("Example 25") +color1 = RGB(red=1, green=2, blue=3) +print(color1) + + +print("Example 26") +color2 = DataclassRGB(red=1, green=2, blue=3) +print(color2) + + +print("Example 27") +class RGB: + def __init__(self, red, green, blue): + self.red = red + self.green = green + self.blue = blue + + + def _astuple(self): + return (self.red, self.green, self.blue) + + +print("Example 28") +color1 = RGB(1, 2, 3) +print(color1._astuple()) + + +print("Example 29") +color2 = RGB(*color1._astuple()) +print(color2.red, color2.green, color2.blue) + + +print("Example 30") +@dataclass +class DataclassRGB: + red: int + green: int + blue: int + +from dataclasses import astuple + +color3 = DataclassRGB(1, 2, 3) +print(astuple(color3)) + + +print("Example 31") +class RGB: + def __init__(self, red, green, blue): + self.red = red + self.green = green + self.blue = blue + + def __repr__(self): + return ( + f"{type(self).__module__}" + f".{type(self).__name__}(" + f"red={self.red!r}, " + f"green={self.green!r}, " + f"blue={self.blue!r})" + ) + + + def _asdict(self): + return dict( + red=self.red, + green=self.green, + blue=self.blue, + ) + + +print("Example 32") +import json + +color1 = RGB(red=1, green=2, blue=3) +data = json.dumps(color1._asdict()) +print(data) + + +print("Example 33") +color2 = RGB(**color1._asdict()) +print(color2) + + +print("Example 34") +from dataclasses import asdict + +color3 = DataclassRGB(red=1, green=2, blue=3) +print(asdict(color3)) + + +print("Example 35") +color1 = RGB(1, 2, 3) +color2 = RGB(1, 2, 3) +print(color1 == color2) + + +print("Example 36") +assert color1 == color1 +assert color1 is color1 +assert color1 != color2 +assert color1 is not color2 + + +print("Example 37") +class RGB: + def __init__(self, red, green, blue): + self.red = red + self.green = green + self.blue = blue + + def __repr__(self): + return ( + f"{type(self).__module__}" + f".{type(self).__name__}(" + f"red={self.red!r}, " + f"green={self.green!r}, " + f"blue={self.blue!r})" + ) + + def _astuple(self): + return (self.red, self.green, self.blue) + + + def __eq__(self, other): + return ( + type(self) == type(other) + and self._astuple() == other._astuple() + ) + + +print("Example 38") +color1 = RGB(1, 2, 3) +color2 = RGB(1, 2, 3) +color3 = RGB(5, 6, 7) +assert color1 == color1 +assert color1 == color2 +assert color1 is not color2 +assert color1 != color3 + + +print("Example 39") +color4 = DataclassRGB(1, 2, 3) +color5 = DataclassRGB(1, 2, 3) +color6 = DataclassRGB(5, 6, 7) +assert color4 == color4 +assert color4 == color5 +assert color4 is not color5 +assert color4 != color6 + + +print("Example 40") +class Planet: + def __init__(self, distance, size): + self.distance = distance + self.size = size + + def __repr__(self): + return ( + f"{type(self).__module__}" + f"{type(self).__name__}(" + f"distance={self.distance}, " + f"size={self.size})" + ) + + +print("Example 41") +try: + far = Planet(10, 5) + near = Planet(1, 2) + data = [far, near] + data.sort() +except: + logging.exception('Expected') +else: + assert False + + +print("Example 42") +class Planet: + def __init__(self, distance, size): + self.distance = distance + self.size = size + + def __repr__(self): + return ( + f"{type(self).__module__}" + f"{type(self).__name__}(" + f"distance={self.distance}, " + f"size={self.size})" + ) + + + def _astuple(self): + return (self.distance, self.size) + + def __eq__(self, other): + return ( + type(self) == type(other) + and self._astuple() == other._astuple() + ) + + def __lt__(self, other): + if type(self) != type(other): + return NotImplemented + return self._astuple() < other._astuple() + + def __le__(self, other): + if type(self) != type(other): + return NotImplemented + return self._astuple() <= other._astuple() + + def __gt__(self, other): + if type(self) != type(other): + return NotImplemented + return self._astuple() > other._astuple() + + def __ge__(self, other): + if type(self) != type(other): + return NotImplemented + return self._astuple() >= other._astuple() + + +# Verify that NotImplemented works correctly +try: + Planet(5, 10) > 8 +except TypeError: + pass +else: + assert False + + +print("Example 43") +far = Planet(10, 2) +near = Planet(1, 5) +data = [far, near] +data.sort() +print(data) + + +print("Example 44") +@dataclass(order=True) +class DataclassPlanet: + distance: float + size: float + + +print("Example 45") +far2 = DataclassPlanet(10, 2) +near2 = DataclassPlanet(1, 5) +assert far2 > near2 +assert near2 < far2 diff --git a/example_code/item_051_example_04.py b/example_code/item_051_example_04.py new file mode 100755 index 0000000..eb8f3db --- /dev/null +++ b/example_code/item_051_example_04.py @@ -0,0 +1,31 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 4") +# Check types in this file with: python -m mypy + +from dataclasses import dataclass + +@dataclass +class DataclassRGB: + red: int + green: int + blue: int + +obj = DataclassRGB(1, "bad", 3) +obj.red = "also bad" diff --git a/example_code/item_051_example_05.py b/example_code/item_051_example_05.py new file mode 100755 index 0000000..16ee66f --- /dev/null +++ b/example_code/item_051_example_05.py @@ -0,0 +1,32 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 5") +# Check types in this file with: python -m mypy + +class RGB: + def __init__( + self, red: int, green: int, blue: int + ) -> None: # Changed + self.red = red + self.green = green + self.blue = blue + + +obj = RGB(1, "bad", 3) +obj.red = "also bad" diff --git a/example_code/item_052.py b/example_code/item_052.py new file mode 100755 index 0000000..acffdf5 --- /dev/null +++ b/example_code/item_052.py @@ -0,0 +1,213 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class InputData: + def read(self): + raise NotImplementedError + + +print("Example 2") +class PathInputData(InputData): + def __init__(self, path): + super().__init__() + self.path = path + + def read(self): + with open(self.path) as f: + return f.read() + + +print("Example 3") +class Worker: + def __init__(self, input_data): + self.input_data = input_data + self.result = None + + def map(self): + raise NotImplementedError + + def reduce(self, other): + raise NotImplementedError + + +print("Example 4") +class LineCountWorker(Worker): + def map(self): + data = self.input_data.read() + self.result = data.count("\n") + + def reduce(self, other): + self.result += other.result + + +print("Example 5") +import os + +def generate_inputs(data_dir): + for name in os.listdir(data_dir): + yield PathInputData(os.path.join(data_dir, name)) + + +print("Example 6") +def create_workers(input_list): + workers = [] + for input_data in input_list: + workers.append(LineCountWorker(input_data)) + return workers + + +print("Example 7") +from threading import Thread + +def execute(workers): + threads = [Thread(target=w.map) for w in workers] + for thread in threads: + thread.start() + for thread in threads: + thread.join() + + first, *rest = workers + for worker in rest: + first.reduce(worker) + return first.result + + +print("Example 8") +def mapreduce(data_dir): + inputs = generate_inputs(data_dir) + workers = create_workers(inputs) + return execute(workers) + + +print("Example 9") +import os +import random + +def write_test_files(tmpdir): + os.makedirs(tmpdir) + for i in range(100): + with open(os.path.join(tmpdir, str(i)), "w") as f: + f.write("\n" * random.randint(0, 100)) + +tmpdir = "test_inputs" +write_test_files(tmpdir) + +result = mapreduce(tmpdir) +print(f"There are {result} lines") + + +print("Example 10") +class GenericInputData: + def read(self): + raise NotImplementedError + + @classmethod + def generate_inputs(cls, config): + raise NotImplementedError + + +print("Example 11") +class PathInputData(GenericInputData): + def __init__(self, path): + super().__init__() + self.path = path + + def read(self): + with open(self.path) as f: + return f.read() + + + @classmethod + def generate_inputs(cls, config): + data_dir = config["data_dir"] + for name in os.listdir(data_dir): + yield cls(os.path.join(data_dir, name)) + + +print("Example 12") +class GenericWorker: + def __init__(self, input_data): + self.input_data = input_data + self.result = None + + def map(self): + raise NotImplementedError + + def reduce(self, other): + raise NotImplementedError + + @classmethod + def create_workers(cls, input_class, config): + workers = [] + for input_data in input_class.generate_inputs(config): + workers.append(cls(input_data)) + return workers + + +print("Example 13") +class LineCountWorker(GenericWorker): # Changed + def map(self): + data = self.input_data.read() + self.result = data.count("\n") + + def reduce(self, other): + self.result += other.result + + +print("Example 14") +def mapreduce(worker_class, input_class, config): + workers = worker_class.create_workers(input_class, config) + return execute(workers) + + +print("Example 15") +config = {"data_dir": tmpdir} +result = mapreduce(LineCountWorker, PathInputData, config) +print(f"There are {result} lines") diff --git a/example_code/item_053.py b/example_code/item_053.py new file mode 100755 index 0000000..59495f5 --- /dev/null +++ b/example_code/item_053.py @@ -0,0 +1,170 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class MyBaseClass: + def __init__(self, value): + self.value = value + +class MyChildClass(MyBaseClass): + def __init__(self): + MyBaseClass.__init__(self, 5) + + +print("Example 2") +class TimesTwo: + def __init__(self): + self.value *= 2 + +class PlusFive: + def __init__(self): + self.value += 5 + + +print("Example 3") +class OneWay(MyBaseClass, TimesTwo, PlusFive): + def __init__(self, value): + MyBaseClass.__init__(self, value) + TimesTwo.__init__(self) + PlusFive.__init__(self) + + +print("Example 4") +foo = OneWay(5) +print("First ordering value is (5 * 2) + 5 =", foo.value) + + +print("Example 5") +class AnotherWay(MyBaseClass, PlusFive, TimesTwo): + def __init__(self, value): + MyBaseClass.__init__(self, value) + TimesTwo.__init__(self) + PlusFive.__init__(self) + + +print("Example 6") +bar = AnotherWay(5) +print("Second ordering should be (5 + 5) * 2, but is", bar.value) + + +print("Example 7") +class TimesSeven(MyBaseClass): + def __init__(self, value): + MyBaseClass.__init__(self, value) + self.value *= 7 + +class PlusNine(MyBaseClass): + def __init__(self, value): + MyBaseClass.__init__(self, value) + self.value += 9 + + +print("Example 8") +class ThisWay(TimesSeven, PlusNine): + def __init__(self, value): + TimesSeven.__init__(self, value) + PlusNine.__init__(self, value) + +foo = ThisWay(5) +print("Should be (5 * 7) + 9 = 44 but is", foo.value) + + +print("Example 9") +class MyBaseClass: + def __init__(self, value): + self.value = value + +class TimesSevenCorrect(MyBaseClass): + def __init__(self, value): + super().__init__(value) + self.value *= 7 + +class PlusNineCorrect(MyBaseClass): + def __init__(self, value): + super().__init__(value) + self.value += 9 + + +print("Example 10") +class GoodWay(TimesSevenCorrect, PlusNineCorrect): + def __init__(self, value): + super().__init__(value) + +foo = GoodWay(5) +print("Should be 7 * (5 + 9) = 98 and is", foo.value) + + +print("Example 11") +mro_str = "\n".join(repr(cls) for cls in GoodWay.__mro__) +print(mro_str) + + +print("Example 12") +class ExplicitTrisect(MyBaseClass): + def __init__(self, value): + super(ExplicitTrisect, self).__init__(value) + self.value /= 3 + +assert ExplicitTrisect(9).value == 3 + + +print("Example 13") +class AutomaticTrisect(MyBaseClass): + def __init__(self, value): + super(__class__, self).__init__(value) + self.value /= 3 + +class ImplicitTrisect(MyBaseClass): + def __init__(self, value): + super().__init__(value) + self.value /= 3 + +assert ExplicitTrisect(9).value == 3 +assert AutomaticTrisect(9).value == 3 +assert ImplicitTrisect(9).value == 3 diff --git a/example_code/item_054.py b/example_code/item_054.py new file mode 100755 index 0000000..4e6acfe --- /dev/null +++ b/example_code/item_054.py @@ -0,0 +1,183 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class ToDictMixin: + def to_dict(self): + return self._traverse_dict(self.__dict__) + + def _traverse_dict(self, instance_dict): + output = {} + for key, value in instance_dict.items(): + output[key] = self._traverse(key, value) + return output + + def _traverse(self, key, value): + if isinstance(value, ToDictMixin): + return value.to_dict() + elif isinstance(value, dict): + return self._traverse_dict(value) + elif isinstance(value, list): + return [self._traverse(key, i) for i in value] + elif hasattr(value, "__dict__"): + return self._traverse_dict(value.__dict__) + else: + return value + + +print("Example 2") +class BinaryTree(ToDictMixin): + def __init__(self, value, left=None, right=None): + self.value = value + self.left = left + self.right = right + + +print("Example 3") +tree = BinaryTree( + 10, + left=BinaryTree(7, right=BinaryTree(9)), + right=BinaryTree(13, left=BinaryTree(11)), +) +orig_print = print +print = pprint +print(tree.to_dict()) +print = orig_print + + +print("Example 4") +class BinaryTreeWithParent(BinaryTree): + def __init__( + self, + value, + left=None, + right=None, + parent=None, + ): + super().__init__(value, left=left, right=right) + self.parent = parent + + def _traverse(self, key, value): + if ( + isinstance(value, BinaryTreeWithParent) + and key == "parent" + ): + return value.value # Prevent cycles + else: + return super()._traverse(key, value) + + +print("Example 5") +root = BinaryTreeWithParent(10) +root.left = BinaryTreeWithParent(7, parent=root) +root.left.right = BinaryTreeWithParent(9, parent=root.left) +orig_print = print +print = pprint +print(root.to_dict()) +print = orig_print + + +print("Example 6") +class NamedSubTree(ToDictMixin): + def __init__(self, name, tree_with_parent): + self.name = name + self.tree_with_parent = tree_with_parent + +my_tree = NamedSubTree("foobar", root.left.right) +orig_print = print +print = pprint +print(my_tree.to_dict()) # No infinite loop +print = orig_print + + +print("Example 7") +import json + +class JsonMixin: + @classmethod + def from_json(cls, data): + kwargs = json.loads(data) + return cls(**kwargs) + + def to_json(self): + return json.dumps(self.to_dict()) + + +print("Example 8") +class DatacenterRack(ToDictMixin, JsonMixin): + def __init__(self, switch=None, machines=None): + self.switch = Switch(**switch) + self.machines = [ + Machine(**kwargs) for kwargs in machines] + +class Switch(ToDictMixin, JsonMixin): + def __init__(self, ports=None, speed=None): + self.ports = ports + self.speed = speed + +class Machine(ToDictMixin, JsonMixin): + def __init__(self, cores=None, ram=None, disk=None): + self.cores = cores + self.ram = ram + self.disk = disk + + +print("Example 9") +serialized = """{ + "switch": {"ports": 5, "speed": 1e9}, + "machines": [ + {"cores": 8, "ram": 32e9, "disk": 5e12}, + {"cores": 4, "ram": 16e9, "disk": 1e12}, + {"cores": 2, "ram": 4e9, "disk": 500e9} + ] +}""" + +deserialized = DatacenterRack.from_json(serialized) +roundtrip = deserialized.to_json() +assert json.loads(serialized) == json.loads(roundtrip) diff --git a/example_code/item_055.py b/example_code/item_055.py new file mode 100755 index 0000000..5cb93a7 --- /dev/null +++ b/example_code/item_055.py @@ -0,0 +1,217 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class MyObject: + def __init__(self): + self.public_field = 5 + self.__private_field = 10 + + def get_private_field(self): + return self.__private_field + + +print("Example 2") +foo = MyObject() +assert foo.public_field == 5 + + +print("Example 3") +assert foo.get_private_field() == 10 + + +print("Example 4") +try: + foo.__private_field +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +class MyOtherObject: + def __init__(self): + self.__private_field = 71 + + @classmethod + def get_private_field_of_instance(cls, instance): + return instance.__private_field + +bar = MyOtherObject() +assert MyOtherObject.get_private_field_of_instance(bar) == 71 + + +print("Example 6") +try: + class MyParentObject: + def __init__(self): + self.__private_field = 71 + + class MyChildObject(MyParentObject): + def get_private_field(self): + return self.__private_field + + baz = MyChildObject() + baz.get_private_field() +except: + logging.exception('Expected') +else: + assert False + + +print("Example 7") +assert baz._MyParentObject__private_field == 71 + + +print("Example 8") +print(baz.__dict__) + + +print("Example 9") +class MyStringClass: + def __init__(self, value): + self.__value = value + + def get_value(self): + return str(self.__value) + +foo = MyStringClass(5) +assert foo.get_value() == "5" + + +print("Example 10") +class MyIntegerSubclass(MyStringClass): + def get_value(self): + return int(self._MyStringClass__value) + +foo = MyIntegerSubclass("5") +assert foo.get_value() == 5 + + +print("Example 11") +class MyBaseClass: + def __init__(self, value): + self.__value = value + + def get_value(self): + return self.__value + +class MyStringClass(MyBaseClass): + def get_value(self): + return str(super().get_value()) # Updated + +class MyIntegerSubclass(MyStringClass): + def get_value(self): + return int(self._MyStringClass__value) # Not updated + + +print("Example 12") +try: + foo = MyIntegerSubclass(5) + foo.get_value() +except: + logging.exception('Expected') +else: + assert False + + +print("Example 13") +class MyStringClass: + def __init__(self, value): + # This stores the user-supplied value for the object. + # It should be coercible to a string. Once assigned in + # the object it should be treated as immutable. + self._value = value + + + def get_value(self): + return str(self._value) + + +class MyIntegerSubclass(MyStringClass): + def get_value(self): + return self._value + +foo = MyIntegerSubclass(5) +assert foo.get_value() == 5 + + +print("Example 14") +class ApiClass: + def __init__(self): + self._value = 5 + + def get(self): + return self._value + +class Child(ApiClass): + def __init__(self): + super().__init__() + self._value = "hello" # Conflicts + +a = Child() +print(f"{a.get()} and {a._value} should be different") + + +print("Example 15") +class ApiClass: + def __init__(self): + self.__value = 5 # Double underscore + + def get(self): + return self.__value # Double underscore + +class Child(ApiClass): + def __init__(self): + super().__init__() + self._value = "hello" # OK! + +a = Child() +print(f"{a.get()} and {a._value} are different") diff --git a/example_code/item_056.py b/example_code/item_056.py new file mode 100755 index 0000000..d6dd59c --- /dev/null +++ b/example_code/item_056.py @@ -0,0 +1,392 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class Point: + def __init__(self, name, x, y): + self.name = name + self.x = x + self.y = y + + +print("Example 2") +def distance(left, right): + return ((left.x - right.x) ** 2 + (left.y - right.y) ** 2) ** 0.5 + +origin1 = Point("source", 0, 0) +point1 = Point("destination", 3, 4) +print(distance(origin1, point1)) + + +print("Example 3") +def bad_distance(left, right): + left.x = -3 + return distance(left, right) + + +print("Example 4") +print(bad_distance(origin1, point1)) +print(origin1.x) + + +print("Example 5") +class ImmutablePoint: + def __init__(self, name, x, y): + self.__dict__.update(name=name, x=x, y=y) + + def __setattr__(self, key, value): + raise AttributeError("Immutable object: set not allowed") + + def __delattr__(self, key): + raise AttributeError("Immutable object: del not allowed") + + +# Verify del is also prevented +try: + point = ImmutablePoint("foo", 5, 10) + del point.x +except AttributeError as e: + assert str(e) == "Immutable object: del not allowed" +else: + assert False + + +print("Example 6") +origin2 = ImmutablePoint("source", 0, 0) +assert distance(origin2, point1) == 5 + + +print("Example 7") +try: + bad_distance(origin2, point1) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +from dataclasses import dataclass + +@dataclass(frozen=True) +class DataclassImmutablePoint: + name: str + x: float + y: float + + +print("Example 9") +origin3 = DataclassImmutablePoint("origin", 0, 0) +assert distance(origin3, point1) == 5 + + +print("Example 10") +try: + bad_distance(origin3, point1) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 13") +from typing import Any, Final, Never + +class ImmutablePoint: + name: Final[str] + x: Final[int] + y: Final[int] + + def __init__(self, name: str, x: int, y: int) -> None: + self.name = name + self.x = x + self.y = y + + def __setattr__(self, key: str, value: Any) -> None: + if key in self.__annotations__ and key not in dir(self): + # Allow the very first assignment to happen + super().__setattr__(key, value) + else: + raise AttributeError("Immutable object: set not allowed") + + def __delattr__(self, key: str) -> Never: + raise AttributeError("Immutable object: del not allowed") + +# Verify set is also prevented +try: + point = ImmutablePoint("foo", 5, 10) + point.x = -3 +except AttributeError as e: + assert str(e) == "Immutable object: set not allowed" +else: + assert False + +# Verify del is also prevented +try: + point = ImmutablePoint("foo", 5, 10) + del point.x +except AttributeError as e: + assert str(e) == "Immutable object: del not allowed" +else: + assert False + + +print("Example 14") +def translate(point, delta_x, delta_y): + point.x += delta_x + point.y += delta_y + + +print("Example 15") +try: + point1 = ImmutablePoint("destination", 5, 3) + translate(point1, 10, 20) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 16") +def translate_copy(point, delta_x, delta_y): + return ImmutablePoint( + name=point.name, + x=point.x + delta_x, + y=point.y + delta_y, + ) + + +point1 = ImmutablePoint("destination", 5, 3) +point2 = translate_copy(point1, 10, 20) +assert point1.x == 5 and point1.y == 3 +assert point2.x == 15 and point2.y == 23 + + +print("Example 17") +class ImmutablePoint: + def __init__(self, name, x, y): + self.__dict__.update(name=name, x=x, y=y) + + def __setattr__(self, key, value): + raise AttributeError("Immutable object: set not allowed") + + def __delattr__(self, key): + raise AttributeError("Immutable object: del not allowed") + + + def _replace(self, **overrides): + fields = dict( + name=self.name, + x=self.x, + y=self.y, + ) + fields.update(overrides) + cls = type(self) + return cls(**fields) + + +print("Example 18") +def translate_replace(point, delta_x, delta_y): + return point._replace( # Changed + x=point.x + delta_x, + y=point.y + delta_y, + ) + + +point3 = ImmutablePoint("destination", 5, 3) +point4 = translate_replace(point3, 10, 20) +assert point3.x == 5 and point3.y == 3 +assert point4.x == 15 and point4.y == 23 + + +print("Example 19") +import dataclasses + +def translate_dataclass(point, delta_x, delta_y): + return dataclasses.replace( # Changed + point, + x=point.x + delta_x, + y=point.y + delta_y, + ) + + +point5 = DataclassImmutablePoint("destination", 5, 3) +point6 = translate_dataclass(point5, 10, 20) +assert point5.x == 5 and point5.y == 3 +assert point6.x == 15 and point6.y == 23 + + +print("Example 20") +my_dict = {} +my_dict["a"] = 123 +my_dict["a"] = 456 +print(my_dict) + + +print("Example 21") +my_set = set() +my_set.add("b") +my_set.add("b") +print(my_set) + + +print("Example 22") +class Point: + def __init__(self, name, x, y): + self.name = name + self.x = x + self.y = y + +point1 = Point("A", 5, 10) +point2 = Point("B", -7, 4) +charges = { + point1: 1.5, + point2: 3.5, +} + + +print("Example 23") +print(charges[point1]) + + +print("Example 24") +try: + point3 = Point("A", 5, 10) + assert point1.x == point3.x + assert point1.y == point3.y + charges[point3] +except: + logging.exception('Expected') +else: + assert False + + +print("Example 25") +assert point1 != point3 + + +print("Example 26") +class Point: + def __init__(self, name, x, y): + self.name = name + self.x = x + self.y = y + + + def __eq__(self, other): + return ( + type(self) == type(other) + and self.name == other.name + and self.x == other.x + and self.y == other.y + ) + + +print("Example 27") +point4 = Point("A", 5, 10) +point5 = Point("A", 5, 10) +assert point4 == point5 + + +print("Example 28") +try: + other_charges = { + point4: 1.5, + } + other_charges[point5] +except: + logging.exception('Expected') +else: + assert False + + +print("Example 29") +class Point: + def __init__(self, name, x, y): + self.name = name + self.x = x + self.y = y + + def __eq__(self, other): + return ( + type(self) == type(other) + and self.name == other.name + and self.x == other.x + and self.y == other.y + ) + + + def __hash__(self): + return hash((self.name, self.x, self.y)) + + +print("Example 30") +point6 = Point("A", 5, 10) +point7 = Point("A", 5, 10) + +more_charges = { + point6: 1.5, +} +value = more_charges[point7] +assert value == 1.5 + + +print("Example 31") +point8 = DataclassImmutablePoint("A", 5, 10) +point9 = DataclassImmutablePoint("A", 5, 10) + +easy_charges = { + point8: 1.5, +} +assert easy_charges[point9] == 1.5 + + +print("Example 32") +my_set = {point8, point9} +assert my_set == {point8} diff --git a/example_code/item_056_example_11.py b/example_code/item_056_example_11.py new file mode 100755 index 0000000..90fec69 --- /dev/null +++ b/example_code/item_056_example_11.py @@ -0,0 +1,31 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 11") +# Check types in this file with: python -m mypy + +from dataclasses import dataclass + +@dataclass(frozen=True) +class DataclassImmutablePoint: + name: str + x: float + y: float + +origin = DataclassImmutablePoint("origin", 0, 0) +origin.x = -3 diff --git a/example_code/item_056_example_12.py b/example_code/item_056_example_12.py new file mode 100755 index 0000000..3423cf5 --- /dev/null +++ b/example_code/item_056_example_12.py @@ -0,0 +1,46 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 12") +# Check types in this file with: python -m mypy + +from typing import Any, Final, Never + +class ImmutablePoint: + name: Final[str] + x: Final[int] + y: Final[int] + + def __init__(self, name: str, x: int, y: int) -> None: + self.name = name + self.x = x + self.y = y + + def __setattr__(self, key: str, value: Any) -> None: + if key in self.__annotations__ and key not in dir(self): + # Allow the very first assignment to happen + super().__setattr__(key, value) + else: + raise AttributeError("Immutable object") + + def __delattr__(self, key: str) -> Never: + raise AttributeError("Immutable object") + + +origin = ImmutablePoint("origin", 0, 0) +origin.x = -3 diff --git a/example_code/item_057.py b/example_code/item_057.py new file mode 100755 index 0000000..5f60b20 --- /dev/null +++ b/example_code/item_057.py @@ -0,0 +1,202 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class FrequencyList(list): + def __init__(self, members): + super().__init__(members) + + def frequency(self): + counts = {} + for item in self: + counts[item] = counts.get(item, 0) + 1 + return counts + + +print("Example 2") +foo = FrequencyList(["a", "b", "a", "c", "b", "a", "d"]) +print("Length is", len(foo)) +foo.pop() # Removes "d" +print("After pop:", repr(foo)) +print("Frequency:", foo.frequency()) + + +print("Example 3") +class BinaryNode: + def __init__(self, value, left=None, right=None): + self.value = value + self.left = left + self.right = right + + +print("Example 4") +bar = [1, 2, 3] +bar[0] + + +print("Example 5") +bar.__getitem__(0) + + +print("Example 6") +class IndexableNode(BinaryNode): + def _traverse(self): + if self.left is not None: + yield from self.left._traverse() + yield self + if self.right is not None: + yield from self.right._traverse() + + def __getitem__(self, index): + for i, item in enumerate(self._traverse()): + if i == index: + return item.value + raise IndexError(f"Index {index} is out of range") + + +print("Example 7") +tree = IndexableNode( + 10, + left=IndexableNode( + 5, + left=IndexableNode(2), + right=IndexableNode(6, right=IndexableNode(7)), + ), + right=IndexableNode(15, left=IndexableNode(11)), +) + + +print("Example 8") +print("LRR is", tree.left.right.right.value) +print("Index 0 is", tree[0]) +print("Index 1 is", tree[1]) +print("11 in the tree?", 11 in tree) +print("17 in the tree?", 17 in tree) +print("Tree is", list(tree)) + +try: + tree[100] +except IndexError: + pass +else: + assert False + + +print("Example 9") +try: + len(tree) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 10") +class SequenceNode(IndexableNode): + def __len__(self): + count = 0 + for _ in self._traverse(): + count += 1 + return count + + +print("Example 11") +tree = SequenceNode( + 10, + left=SequenceNode( + 5, + left=SequenceNode(2), + right=SequenceNode(6, right=SequenceNode(7)), + ), + right=SequenceNode(15, left=SequenceNode(11)), +) + +print("Tree length is", len(tree)) + + +print("Example 12") +try: + # Make sure that this doesn't work + tree.count(4) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 13") +try: + from collections.abc import Sequence + + class BadType(Sequence): + pass + + foo = BadType() +except: + logging.exception('Expected') +else: + assert False + + +print("Example 14") +class BetterNode(SequenceNode, Sequence): + pass + +tree = BetterNode( + 10, + left=BetterNode( + 5, + left=BetterNode(2), + right=BetterNode(6, right=BetterNode(7)), + ), + right=BetterNode(15, left=BetterNode(11)), +) + +print("Index of 7 is", tree.index(7)) +print("Count of 10 is", tree.count(10)) diff --git a/example_code/item_058.py b/example_code/item_058.py new file mode 100755 index 0000000..776d49e --- /dev/null +++ b/example_code/item_058.py @@ -0,0 +1,193 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class OldResistor: + def __init__(self, ohms): + self._ohms = ohms + + def get_ohms(self): + return self._ohms + + def set_ohms(self, ohms): + self._ohms = ohms + + +print("Example 2") +r0 = OldResistor(50e3) +print("Before:", r0.get_ohms()) +r0.set_ohms(10e3) +print("After: ", r0.get_ohms()) + + +print("Example 3") +r0.set_ohms(r0.get_ohms() - 4e3) +assert r0.get_ohms() == 6e3 + + +print("Example 4") +class Resistor: + def __init__(self, ohms): + self.ohms = ohms + self.voltage = 0 + self.current = 0 + +r1 = Resistor(50e3) +r1.ohms = 10e3 +print( + f"{r1.ohms} ohms, " f"{r1.voltage} volts, " f"{r1.current} amps" +) + + +print("Example 5") +r1.ohms += 5e3 + + +print("Example 6") +class VoltageResistance(Resistor): + def __init__(self, ohms): + super().__init__(ohms) + self._voltage = 0 + + @property + def voltage(self): + return self._voltage + + @voltage.setter + def voltage(self, voltage): + self._voltage = voltage + self.current = self._voltage / self.ohms + + +print("Example 7") +r2 = VoltageResistance(1e2) +print(f"Before: {r2.current:.2f} amps") +r2.voltage = 10 +print(f"After: {r2.current:.2f} amps") + + +print("Example 8") +class BoundedResistance(Resistor): + def __init__(self, ohms): + super().__init__(ohms) + + @property + def ohms(self): + return self._ohms + + @ohms.setter + def ohms(self, ohms): + if ohms <= 0: + raise ValueError(f"ohms must be > 0; got {ohms}") + self._ohms = ohms + + +print("Example 9") +try: + r3 = BoundedResistance(1e3) + r3.ohms = 0 +except: + logging.exception('Expected') +else: + assert False + + +print("Example 10") +try: + BoundedResistance(-5) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 11") +class FixedResistance(Resistor): + def __init__(self, ohms): + super().__init__(ohms) + + @property + def ohms(self): + return self._ohms + + @ohms.setter + def ohms(self, ohms): + if hasattr(self, "_ohms"): + raise AttributeError("Ohms is immutable") + self._ohms = ohms + + +print("Example 12") +try: + r4 = FixedResistance(1e3) + r4.ohms = 2e3 +except: + logging.exception('Expected') +else: + assert False + + +print("Example 13") +class MysteriousResistor(Resistor): + @property + def ohms(self): + self.voltage = self._ohms * self.current + return self._ohms + + @ohms.setter + def ohms(self, ohms): + self._ohms = ohms + + +print("Example 14") +r7 = MysteriousResistor(10) +r7.current = 0.1 +print(f"Before: {r7.voltage:.2f}") +r7.ohms +print(f"After: {r7.voltage:.2f}") diff --git a/example_code/item_059.py b/example_code/item_059.py new file mode 100755 index 0000000..05d1b6b --- /dev/null +++ b/example_code/item_059.py @@ -0,0 +1,210 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +from datetime import datetime, timedelta + +class Bucket: + def __init__(self, period): + self.period_delta = timedelta(seconds=period) + self.reset_time = datetime.now() + self.quota = 0 + + def __repr__(self): + return f"Bucket(quota={self.quota})" + + +bucket = Bucket(60) +print(bucket) + + +print("Example 2") +def fill(bucket, amount): + now = datetime.now() + if (now - bucket.reset_time) > bucket.period_delta: + bucket.quota = 0 + bucket.reset_time = now + bucket.quota += amount + + +print("Example 3") +def deduct(bucket, amount): + now = datetime.now() + if (now - bucket.reset_time) > bucket.period_delta: + return False # Bucket hasn't been filled this period + if bucket.quota - amount < 0: + return False # Bucket was filled, but not enough + bucket.quota -= amount + return True # Bucket had enough, quota consumed + + +print("Example 4") +bucket = Bucket(60) +fill(bucket, 100) +print(bucket) + + +print("Example 5") +if deduct(bucket, 99): + print("Had 99 quota") +else: + print("Not enough for 99 quota") + +print(bucket) + + +print("Example 6") +if deduct(bucket, 3): + print("Had 3 quota") +else: + print("Not enough for 3 quota") + +print(bucket) + + +print("Example 7") +class NewBucket: + def __init__(self, period): + self.period_delta = timedelta(seconds=period) + self.reset_time = datetime.now() + self.max_quota = 0 + self.quota_consumed = 0 + + def __repr__(self): + return ( + f"NewBucket(max_quota={self.max_quota}, " + f"quota_consumed={self.quota_consumed})" + ) + + +print("Example 8") + @property + def quota(self): + return self.max_quota - self.quota_consumed + + +print("Example 9") + @quota.setter + def quota(self, amount): + delta = self.max_quota - amount + if amount == 0: + # Quota being reset for a new period + self.quota_consumed = 0 + self.max_quota = 0 + elif delta < 0: + # Quota being filled during the period + self.max_quota = amount + self.quota_consumed + else: + # Quota being consumed during the period + self.quota_consumed = delta + + +print("Example 10") +bucket = NewBucket(60) +print("Initial", bucket) +fill(bucket, 100) +print("Filled", bucket) + +if deduct(bucket, 99): + print("Had 99 quota") +else: + print("Not enough for 99 quota") + +print("Now", bucket) + +if deduct(bucket, 3): + print("Had 3 quota") +else: + print("Not enough for 3 quota") + +print("Still", bucket) + + +print("Example 11") +bucket = NewBucket(6000) +assert bucket.max_quota == 0 +assert bucket.quota_consumed == 0 +assert bucket.quota == 0 + +fill(bucket, 100) +assert bucket.max_quota == 100 +assert bucket.quota_consumed == 0 +assert bucket.quota == 100 + +assert deduct(bucket, 10) +assert bucket.max_quota == 100 +assert bucket.quota_consumed == 10 +assert bucket.quota == 90 + +assert deduct(bucket, 20) +assert bucket.max_quota == 100 +assert bucket.quota_consumed == 30 +assert bucket.quota == 70 + +fill(bucket, 50) +assert bucket.max_quota == 150 +assert bucket.quota_consumed == 30 +assert bucket.quota == 120 + +assert deduct(bucket, 40) +assert bucket.max_quota == 150 +assert bucket.quota_consumed == 70 +assert bucket.quota == 80 + +assert not deduct(bucket, 81) +assert bucket.max_quota == 150 +assert bucket.quota_consumed == 70 +assert bucket.quota == 80 + +bucket.reset_time += bucket.period_delta - timedelta(1) +assert bucket.quota == 80 +assert not deduct(bucket, 79) + +fill(bucket, 1) +assert bucket.quota == 1 diff --git a/example_code/item_060.py b/example_code/item_060.py new file mode 100755 index 0000000..f361eb0 --- /dev/null +++ b/example_code/item_060.py @@ -0,0 +1,233 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class Homework: + def __init__(self): + self._grade = 0 + + @property + def grade(self): + return self._grade + + @grade.setter + def grade(self, value): + if not (0 <= value <= 100): + raise ValueError("Grade must be between 0 and 100") + self._grade = value + + +print("Example 2") +galileo = Homework() +galileo.grade = 95 +assert galileo.grade == 95 + + +print("Example 3") +class Exam: + def __init__(self): + self._writing_grade = 0 + self._math_grade = 0 + + @staticmethod + def _check_grade(value): + if not (0 <= value <= 100): + raise ValueError("Grade must be between 0 and 100") + + +print("Example 4") + @property + def writing_grade(self): + return self._writing_grade + + @writing_grade.setter + def writing_grade(self, value): + self._check_grade(value) + self._writing_grade = value + + @property + def math_grade(self): + return self._math_grade + + @math_grade.setter + def math_grade(self, value): + self._check_grade(value) + self._math_grade = value + +galileo = Exam() +galileo.writing_grade = 85 +galileo.math_grade = 99 + +assert galileo.writing_grade == 85 +assert galileo.math_grade == 99 + + +print("Example 5") +class Grade: + def __get__(self, instance, instance_type): + pass + + def __set__(self, instance, value): + pass + +class Exam: + # Class attributes + math_grade = Grade() + writing_grade = Grade() + science_grade = Grade() + + +print("Example 6") +exam = Exam() +exam.writing_grade = 40 + + +print("Example 7") +Exam.__dict__["writing_grade"].__set__(exam, 40) + + +print("Example 8") +exam.writing_grade + + +print("Example 9") +Exam.__dict__["writing_grade"].__get__(exam, Exam) + + +print("Example 10") +class Grade: + def __init__(self): + self._value = 0 + + def __get__(self, instance, instance_type): + return self._value + + def __set__(self, instance, value): + if not (0 <= value <= 100): + raise ValueError("Grade must be between 0 and 100") + self._value = value + + +print("Example 11") +class Exam: + math_grade = Grade() + writing_grade = Grade() + science_grade = Grade() + +first_exam = Exam() +first_exam.writing_grade = 82 +first_exam.science_grade = 99 +print("Writing", first_exam.writing_grade) +print("Science", first_exam.science_grade) + + +print("Example 12") +second_exam = Exam() +second_exam.writing_grade = 75 +print(f"Second {second_exam.writing_grade} is right") +print(f"First {first_exam.writing_grade} is wrong; " f"should be 82") + + +print("Example 13") +class DictGrade: + def __init__(self): + self._values = {} + + def __get__(self, instance, instance_type): + if instance is None: + return self + return self._values.get(instance, 0) + + def __set__(self, instance, value): + if not (0 <= value <= 100): + raise ValueError("Grade must be between 0 and 100") + self._values[instance] = value + +class DictExam: + math_grade = DictGrade() + writing_grade = DictGrade() + science_grade = DictGrade() + +first_exam = DictExam() +first_exam.math_grade = 78 +second_exam = DictExam() +second_exam.math_grade = 89 +print(first_exam.math_grade) +print(second_exam.math_grade) + + +print("Example 14") +class NamedGrade: + def __set_name__(self, owner, name): + self.internal_name = "_" + name + + +print("Example 15") + def __get__(self, instance, instance_type): + if instance is None: + return self + return getattr(instance, self.internal_name) + + def __set__(self, instance, value): + if not (0 <= value <= 100): + raise ValueError("Grade must be between 0 and 100") + setattr(instance, self.internal_name, value) + + +print("Example 16") +class NamedExam: + math_grade = NamedGrade() + writing_grade = NamedGrade() + science_grade = NamedGrade() + +first_exam = NamedExam() +first_exam.math_grade = 78 +first_exam.writing_grade = 89 +first_exam.science_grade = 94 +print(first_exam.__dict__) diff --git a/example_code/item_061.py b/example_code/item_061.py new file mode 100755 index 0000000..7619deb --- /dev/null +++ b/example_code/item_061.py @@ -0,0 +1,201 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class LazyRecord: + def __init__(self): + self.exists = 5 + + def __getattr__(self, name): + value = f"Value for {name}" + setattr(self, name, value) + return value + + +print("Example 2") +data = LazyRecord() +print("Before:", data.__dict__) +print("foo: ", data.foo) +print("After: ", data.__dict__) + + +print("Example 3") +class LoggingLazyRecord(LazyRecord): + def __getattr__(self, name): + print( + f"* Called __getattr__({name!r}), " + f"populating instance dictionary" + ) + result = super().__getattr__(name) + print(f"* Returning {result!r}") + return result + +data = LoggingLazyRecord() +print("exists: ", data.exists) +print("First foo: ", data.foo) +print("Second foo: ", data.foo) + + +print("Example 4") +class ValidatingRecord: + def __init__(self): + self.exists = 5 + + def __getattribute__(self, name): + print(f"* Called __getattribute__({name!r})") + try: + value = super().__getattribute__(name) + print(f"* Found {name!r}, returning {value!r}") + return value + except AttributeError: + value = f"Value for {name}" + print(f"* Setting {name!r} to {value!r}") + setattr(self, name, value) + return value + +data = ValidatingRecord() +print("exists: ", data.exists) +print("First foo: ", data.foo) +print("Second foo: ", data.foo) + + +print("Example 5") +try: + class MissingPropertyRecord: + def __getattr__(self, name): + if name == "bad_name": + raise AttributeError(f"{name} is missing") + value = f"Value for {name}" + setattr(self, name, value) + return value + + data = MissingPropertyRecord() + assert data.foo == "Value for foo" # Test this works + data.bad_name +except: + logging.exception('Expected') +else: + assert False + + +print("Example 6") +data = LoggingLazyRecord() # Implements __getattr__ +print("Before: ", data.__dict__) +print("Has first foo: ", hasattr(data, "foo")) +print("After: ", data.__dict__) +print("Has second foo: ", hasattr(data, "foo")) + + +print("Example 7") +data = ValidatingRecord() # Implements __getattribute__ +print("Has first foo: ", hasattr(data, "foo")) +print("Has second foo: ", hasattr(data, "foo")) + + +print("Example 8") +class SavingRecord: + def __setattr__(self, name, value): + # Save some data for the record + pass + super().__setattr__(name, value) + + +print("Example 9") +class LoggingSavingRecord(SavingRecord): + def __setattr__(self, name, value): + print(f"* Called __setattr__({name!r}, {value!r})") + super().__setattr__(name, value) + +data = LoggingSavingRecord() +print("Before: ", data.__dict__) +data.foo = 5 +print("After: ", data.__dict__) +data.foo = 7 +print("Finally:", data.__dict__) + + +print("Example 10") +class BrokenDictionaryRecord: + def __init__(self, data): + self._data = data + + def __getattribute__(self, name): + print(f"* Called __getattribute__({name!r})") + return self._data[name] + + +print("Example 11") +try: + import sys + + sys.setrecursionlimit(50) + data = BrokenDictionaryRecord({"foo": 3}) + data.foo +except: + logging.exception('Expected') +else: + assert False + + +print("Example 12") +class DictionaryRecord: + def __init__(self, data): + self._data = data + + def __getattribute__(self, name): + # Prevent weird interactions with isinstance() used + # by example code harness. + if name == "__class__": + return DictionaryRecord + print(f"* Called __getattribute__({name!r})") + data_dict = super().__getattribute__("_data") + return data_dict[name] + +data = DictionaryRecord({"foo": 3}) +print("foo: ", data.foo) diff --git a/example_code/item_062.py b/example_code/item_062.py new file mode 100755 index 0000000..4a3992a --- /dev/null +++ b/example_code/item_062.py @@ -0,0 +1,304 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class Meta(type): + def __new__(meta, name, bases, class_dict): + global print + orig_print = print + print(f"* Running {meta}.__new__ for {name}") + print("Bases:", bases) + print = pprint + print(class_dict) + print = orig_print + return type.__new__(meta, name, bases, class_dict) + +class MyClass(metaclass=Meta): + stuff = 123 + + def foo(self): + pass + +class MySubclass(MyClass): + other = 567 + + def bar(self): + pass + + +print("Example 2") +class ValidatePolygon(type): + def __new__(meta, name, bases, class_dict): + # Only validate subclasses of the Polygon class + if bases: + if class_dict["sides"] < 3: + raise ValueError("Polygons need 3+ sides") + return type.__new__(meta, name, bases, class_dict) + +class Polygon(metaclass=ValidatePolygon): + sides = None # Must be specified by subclasses + + @classmethod + def interior_angles(cls): + return (cls.sides - 2) * 180 + +class Triangle(Polygon): + sides = 3 + +class Rectangle(Polygon): + sides = 4 + +class Nonagon(Polygon): + sides = 9 + +assert Triangle.interior_angles() == 180 +assert Rectangle.interior_angles() == 360 +assert Nonagon.interior_angles() == 1260 + + +print("Example 3") +try: + print("Before class") + + class Line(Polygon): + print("Before sides") + sides = 2 + print("After sides") + + print("After class") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 4") +class BetterPolygon: + sides = None # Must be specified by subclasses + + def __init_subclass__(cls): + super().__init_subclass__() + if cls.sides < 3: + raise ValueError("Polygons need 3+ sides") + + @classmethod + def interior_angles(cls): + return (cls.sides - 2) * 180 + +class Hexagon(BetterPolygon): + sides = 6 + +assert Hexagon.interior_angles() == 720 + + +print("Example 5") +try: + print("Before class") + + class Point(BetterPolygon): + sides = 1 + + print("After class") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 6") +class ValidateFilled(type): + def __new__(meta, name, bases, class_dict): + # Only validate subclasses of the Filled class + if bases: + if class_dict["color"] not in ("red", "green"): + raise ValueError("Fill color must be supported") + return type.__new__(meta, name, bases, class_dict) + +class Filled(metaclass=ValidateFilled): + color = None # Must be specified by subclasses + + +print("Example 7") +try: + class RedPentagon(Filled, Polygon): + color = "blue" + sides = 5 +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +class ValidatePolygon(type): + def __new__(meta, name, bases, class_dict): + # Only validate non-root classes + if not class_dict.get("is_root"): + if class_dict["sides"] < 3: + raise ValueError("Polygons need 3+ sides") + return type.__new__(meta, name, bases, class_dict) + +class Polygon(metaclass=ValidatePolygon): + is_root = True + sides = None # Must be specified by subclasses + +class ValidateFilledPolygon(ValidatePolygon): + def __new__(meta, name, bases, class_dict): + # Only validate non-root classes + if not class_dict.get("is_root"): + if class_dict["color"] not in ("red", "green"): + raise ValueError("Fill color must be supported") + return super().__new__(meta, name, bases, class_dict) + +class FilledPolygon(Polygon, metaclass=ValidateFilledPolygon): + is_root = True + color = None # Must be specified by subclasses + + +print("Example 9") +class GreenPentagon(FilledPolygon): + color = "green" + sides = 5 + +greenie = GreenPentagon() +assert isinstance(greenie, Polygon) + + +print("Example 10") +try: + class OrangePentagon(FilledPolygon): + color = "orange" + sides = 5 +except: + logging.exception('Expected') +else: + assert False + + +print("Example 11") +try: + class RedLine(FilledPolygon): + color = "red" + sides = 2 +except: + logging.exception('Expected') +else: + assert False + + +print("Example 12") +class Filled: + color = None # Must be specified by subclasses + + def __init_subclass__(cls): + super().__init_subclass__() + if cls.color not in ("red", "green", "blue"): + raise ValueError("Fills need a valid color") + + +print("Example 13") +class RedTriangle(Filled, BetterPolygon): + color = "red" + sides = 3 + +ruddy = RedTriangle() +assert isinstance(ruddy, Filled) +assert isinstance(ruddy, BetterPolygon) + + +print("Example 14") +try: + print("Before class") + + class BlueLine(Filled, BetterPolygon): + color = "blue" + sides = 2 + + print("After class") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 15") +try: + print("Before class") + + class BeigeSquare(Filled, BetterPolygon): + color = "beige" + sides = 4 + + print("After class") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 16") +class Top: + def __init_subclass__(cls): + super().__init_subclass__() + print(f"Top for {cls}") + +class Left(Top): + def __init_subclass__(cls): + super().__init_subclass__() + print(f"Left for {cls}") + +class Right(Top): + def __init_subclass__(cls): + super().__init_subclass__() + print(f"Right for {cls}") + +class Bottom(Left, Right): + def __init_subclass__(cls): + super().__init_subclass__() + print(f"Bottom for {cls}") diff --git a/example_code/item_063.py b/example_code/item_063.py new file mode 100755 index 0000000..56d0ff7 --- /dev/null +++ b/example_code/item_063.py @@ -0,0 +1,216 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import json + +class Serializable: + def __init__(self, *args): + self.args = args + + def serialize(self): + return json.dumps({"args": self.args}) + + +print("Example 2") +class Point2D(Serializable): + def __init__(self, x, y): + super().__init__(x, y) + self.x = x + self.y = y + + def __repr__(self): + return f"Point2D({self.x}, {self.y})" + +point = Point2D(5, 3) +print("Object: ", point) +print("Serialized:", point.serialize()) + + +print("Example 3") +class Deserializable(Serializable): + @classmethod + def deserialize(cls, json_data): + params = json.loads(json_data) + return cls(*params["args"]) + + +print("Example 4") +class BetterPoint2D(Deserializable): + def __init__(self, x, y): + super().__init__(x, y) + self.x = x + self.y = y + + def __repr__(self): + return f"Point2D({self.x}, {self.y})" + +before = BetterPoint2D(5, 3) +print("Before: ", before) +data = before.serialize() +print("Serialized:", data) +after = BetterPoint2D.deserialize(data) +print("After: ", after) + + +print("Example 5") +class BetterSerializable: + def __init__(self, *args): + self.args = args + + def serialize(self): + return json.dumps( + { + "class": self.__class__.__name__, + "args": self.args, + } + ) + + def __repr__(self): + name = self.__class__.__name__ + args_str = ", ".join(str(x) for x in self.args) + return f"{name}({args_str})" + + +print("Example 6") +REGISTRY = {} + +def register_class(target_class): + REGISTRY[target_class.__name__] = target_class + +def deserialize(data): + params = json.loads(data) + name = params["class"] + target_class = REGISTRY[name] + return target_class(*params["args"]) + + +print("Example 7") +class EvenBetterPoint2D(BetterSerializable): + def __init__(self, x, y): + super().__init__(x, y) + self.x = x + self.y = y + +register_class(EvenBetterPoint2D) + + +print("Example 8") +before = EvenBetterPoint2D(5, 3) +print("Before: ", before) +data = before.serialize() +print("Serialized:", data) +after = deserialize(data) +print("After: ", after) + + +print("Example 9") +class Point3D(BetterSerializable): + def __init__(self, x, y, z): + super().__init__(x, y, z) + self.x = x + self.y = y + self.z = z + +# Forgot to call register_class! Whoops! + + +print("Example 10") +try: + point = Point3D(5, 9, -4) + data = point.serialize() + deserialize(data) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 11") +class Meta(type): + def __new__(meta, name, bases, class_dict): + cls = type.__new__(meta, name, bases, class_dict) + register_class(cls) + return cls + +class RegisteredSerializable(BetterSerializable, metaclass=Meta): + pass + + +print("Example 12") +class Vector3D(RegisteredSerializable): + def __init__(self, x, y, z): + super().__init__(x, y, z) + self.x, self.y, self.z = x, y, z + +before = Vector3D(10, -7, 3) +print("Before: ", before) +data = before.serialize() +print("Serialized:", data) +print("After: ", deserialize(data)) + + +print("Example 13") +class BetterRegisteredSerializable(BetterSerializable): + def __init_subclass__(cls): + super().__init_subclass__() + register_class(cls) + +class Vector1D(BetterRegisteredSerializable): + def __init__(self, magnitude): + super().__init__(magnitude) + self.magnitude = magnitude + + +print("Example 14") +before = Vector1D(6) +print("Before: ", before) +data = before.serialize() +print("Serialized:", data) +print("After: ", deserialize(data)) diff --git a/example_code/item_064.py b/example_code/item_064.py new file mode 100755 index 0000000..0627cee --- /dev/null +++ b/example_code/item_064.py @@ -0,0 +1,185 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class Field: + def __init__(self, column_name): + self.column_name = column_name + self.internal_name = "_" + self.column_name + + +print("Example 2") + def __get__(self, instance, instance_type): + if instance is None: + return self + return getattr(instance, self.internal_name, "") + + def __set__(self, instance, value): + setattr(instance, self.internal_name, value) + + +print("Example 3") +class Customer: + # Class attributes + first_name = Field("first_name") + last_name = Field("last_name") + prefix = Field("prefix") + suffix = Field("suffix") + + +print("Example 4") +cust = Customer() +print(f"Before: {cust.first_name!r} {cust.__dict__}") +cust.first_name = "Euclid" +print(f"After: {cust.first_name!r} {cust.__dict__}") + + +print("Example 5") +class Customer: + # Left side is redundant with right side + first_name = Field("first_name") + last_name = Field("last_name") + prefix = Field("prefix") + suffix = Field("suffix") + + +print("Example 6") +class Meta(type): + def __new__(meta, name, bases, class_dict): + for key, value in class_dict.items(): + if isinstance(value, Field): + value.column_name = key + value.internal_name = "_" + key + cls = type.__new__(meta, name, bases, class_dict) + return cls + + +print("Example 7") +class DatabaseRow(metaclass=Meta): + pass + + +print("Example 8") +class Field: + def __init__(self): + # These will be assigned by the metaclass. + self.column_name = None + self.internal_name = None + + def __get__(self, instance, instance_type): + if instance is None: + return self + return getattr(instance, self.internal_name, "") + + def __set__(self, instance, value): + setattr(instance, self.internal_name, value) + + +print("Example 9") +class BetterCustomer(DatabaseRow): + first_name = Field() + last_name = Field() + prefix = Field() + suffix = Field() + + +print("Example 10") +cust = BetterCustomer() +print(f"Before: {cust.first_name!r} {cust.__dict__}") +cust.first_name = "Euler" +print(f"After: {cust.first_name!r} {cust.__dict__}") + + +print("Example 11") +try: + class BrokenCustomer: # Missing inheritance + first_name = Field() + last_name = Field() + prefix = Field() + suffix = Field() + + cust = BrokenCustomer() + cust.first_name = "Mersenne" +except: + logging.exception('Expected') +else: + assert False + + +print("Example 12") +class Field: + def __init__(self): + self.column_name = None + self.internal_name = None + + def __set_name__(self, owner, column_name): + # Called on class creation for each descriptor + self.column_name = column_name + self.internal_name = "_" + column_name + + def __get__(self, instance, instance_type): + if instance is None: + return self + return getattr(instance, self.internal_name, "") + + def __set__(self, instance, value): + setattr(instance, self.internal_name, value) + + +print("Example 13") +class FixedCustomer: # No parent class + first_name = Field() + last_name = Field() + prefix = Field() + suffix = Field() + +cust = FixedCustomer() +print(f"Before: {cust.first_name!r} {cust.__dict__}") +cust.first_name = "Mersenne" +print(f"After: {cust.first_name!r} {cust.__dict__}") diff --git a/example_code/item_065.py b/example_code/item_065.py new file mode 100755 index 0000000..4cc5e07 --- /dev/null +++ b/example_code/item_065.py @@ -0,0 +1,305 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import csv + + +with open("packages.csv", "w") as f: + f.write( + """\ +Sydney,truck,25 +Melbourne,boat,6 +Brisbane,plane,12 +Perth,road train,90 +Adelaide,truck,17 +""" + ) + + +with open("packages.csv") as f: + for row in csv.reader(f): + print(row) +print("...") + + +print("Example 2") +class Delivery: + def __init__(self, destination, method, weight): + self.destination = destination + self.method = method + self.weight = weight + + @classmethod + def from_row(cls, row): + return cls(row[0], row[1], row[2]) + + +print("Example 3") +row1 = ["Sydney", "truck", "25"] +obj1 = Delivery.from_row(row1) +print(obj1.__dict__) + + +print("Example 4") +class RowMapper: + fields = () # Must be in CSV column order + + def __init__(self, **kwargs): + for key, value in kwargs.items(): + if key not in type(self).fields: + raise TypeError(f"Invalid field: {key}") + setattr(self, key, value) + + @classmethod + def from_row(cls, row): + if len(row) != len(cls.fields): + raise ValueError("Wrong number of fields") + kwargs = dict(pair for pair in zip(cls.fields, row)) + return cls(**kwargs) + + +print("Example 5") +class DeliveryMapper(RowMapper): + fields = ("destination", "method", "weight") + + +try: + DeliveryMapper.from_row([1, 2, 3, 4]) +except ValueError as e: + assert str(e) == "Wrong number of fields" + +try: + DeliveryMapper(bad=1) +except TypeError as e: + assert str(e) == "Invalid field: bad" + + +obj2 = DeliveryMapper.from_row(row1) +assert obj2.destination == "Sydney" +assert obj2.method == "truck" +assert obj2.weight == "25" + + +print("Example 6") +class MovingMapper(RowMapper): + fields = ("source", "destination", "square_feet") + + +print("Example 7") +class BetterMovingMapper: + source = ... + destination = ... + square_feet = ... + + +print("Example 8") +class BetterRowMapper(RowMapper): + def __init_subclass__(cls): + fields = [] + for key, value in cls.__dict__.items(): + if value is Ellipsis: + fields.append(key) + cls.fields = tuple(fields) + + +print("Example 9") +class BetterDeliveryMapper(BetterRowMapper): + destination = ... + method = ... + weight = ... + + +try: + DeliveryMapper.from_row([1, 2, 3, 4]) +except ValueError as e: + assert str(e) == "Wrong number of fields" + +try: + BetterDeliveryMapper(bad=1) +except TypeError as e: + assert str(e) == "Invalid field: bad" + + +obj3 = BetterDeliveryMapper.from_row(row1) +assert obj3.destination == "Sydney" +assert obj3.method == "truck" +assert obj3.weight == "25" + + +print("Example 10") +class ReorderedDeliveryMapper(BetterRowMapper): + method = ... + weight = ... + destination = ... # Moved + +row4 = ["road train", "90", "Perth"] # Different order +obj4 = ReorderedDeliveryMapper.from_row(row4) +print(obj4.__dict__) + + +print("Example 11") +class Field: + def __init__(self): + self.internal_name = None + + def __set_name__(self, owner, column_name): + self.internal_name = "_" + column_name + + def __get__(self, instance, instance_type): + if instance is None: + return self + return getattr(instance, self.internal_name, "") + + def __set__(self, instance, value): + adjusted_value = self.convert(value) + setattr(instance, self.internal_name, adjusted_value) + + def convert(self, value): + raise NotImplementedError + + +print("Example 12") +class StringField(Field): + def convert(self, value): + if not isinstance(value, str): + raise ValueError + return value + +class FloatField(Field): + def convert(self, value): + return float(value) + + +print("Example 13") +class DescriptorRowMapper(RowMapper): + def __init_subclass__(cls): + fields = [] + for key, value in cls.__dict__.items(): + if isinstance(value, Field): # Changed + fields.append(key) + cls.fields = tuple(fields) + +try: + DescriptorRowMapper.from_row([1, 2, 3, 4]) +except ValueError as e: + assert str(e) == "Wrong number of fields" + +try: + DescriptorRowMapper(bad=1) +except TypeError as e: + assert str(e) == "Invalid field: bad" + + +print("Example 14") +class ConvertingDeliveryMapper(DescriptorRowMapper): + destination = StringField() + method = StringField() + weight = FloatField() + +obj5 = ConvertingDeliveryMapper.from_row(row1) +assert obj5.destination == "Sydney" +assert obj5.method == "truck" +assert obj5.weight == 25.0 # Number, not string + + +print("Example 15") +class HypotheticalWorkflow: + def start_engine(self): + pass + + def release_brake(self): + pass + + def run(self): + # Runs `start_engine` then `release_brake` + pass + + +print("Example 16") +def step(func): + func._is_step = True + return func + + +print("Example 17") +class Workflow: + def __init_subclass__(cls): + steps = [] + for key, value in cls.__dict__.items(): + if callable(value) and hasattr(value, "_is_step"): + steps.append(key) + cls.steps = tuple(steps) + + +print("Example 18") + def run(self): + for step_name in type(self).steps: + func = getattr(self, step_name) + func() + + +print("Example 19") +class MyWorkflow(Workflow): + @step + def start_engine(self): + print("Engine is on!") + + def my_helper_function(self): + raise RuntimeError("Should not be called") + + @step + def release_brake(self): + print("Brake is off!") + + +print("Example 20") +workflow = MyWorkflow() +workflow.run() +print("...") diff --git a/example_code/item_066.py b/example_code/item_066.py new file mode 100755 index 0000000..1d869b8 --- /dev/null +++ b/example_code/item_066.py @@ -0,0 +1,273 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +from functools import wraps + +def trace_func(func): + if hasattr(func, "tracing"): # Only decorate once + return func + + @wraps(func) + def wrapper(*args, **kwargs): + args_repr = repr(args) + kwargs_repr = repr(kwargs) + result = None + try: + result = func(*args, **kwargs) + return result + except Exception as e: + result = e + raise + finally: + print( + f"{func.__name__}" + f"({args_repr}, {kwargs_repr}) -> " + f"{result!r}" + ) + + wrapper.tracing = True + return wrapper + + +print("Example 2") +class TraceDict(dict): + @trace_func + def __init__(self, *args, **kwargs): + return super().__init__(*args, **kwargs) + + @trace_func + def __setitem__(self, *args, **kwargs): + return super().__setitem__(*args, **kwargs) + + @trace_func + def __getitem__(self, *args, **kwargs): + return super().__getitem__(*args, **kwargs) + + +print("Example 3") +trace_dict = TraceDict([("hi", 1)]) +trace_dict["there"] = 2 +trace_dict["hi"] +try: + trace_dict["does not exist"] +except KeyError: + pass # Expected +else: + assert False + + +print("Example 4") +import types + +TRACE_TYPES = ( + types.MethodType, + types.FunctionType, + types.BuiltinFunctionType, + types.BuiltinMethodType, + types.MethodDescriptorType, + types.ClassMethodDescriptorType, + types.WrapperDescriptorType, +) + +IGNORE_METHODS = ( + "__repr__", + "__str__", +) + +class TraceMeta(type): + def __new__(meta, name, bases, class_dict): + klass = super().__new__(meta, name, bases, class_dict) + + for key in dir(klass): + if key in IGNORE_METHODS: + continue + + value = getattr(klass, key) + if not isinstance(value, TRACE_TYPES): + continue + + wrapped = trace_func(value) + setattr(klass, key, wrapped) + + return klass + + +print("Example 5") +class TraceDict(dict, metaclass=TraceMeta): + pass + +trace_dict = TraceDict([("hi", 1)]) +trace_dict["there"] = 2 +trace_dict["hi"] +try: + trace_dict["does not exist"] +except KeyError: + pass # Expected +else: + assert False + + +print("Example 6") +try: + class OtherMeta(type): + pass + + class SimpleDict(dict, metaclass=OtherMeta): + pass + + class ChildTraceDict(SimpleDict, metaclass=TraceMeta): + pass +except: + logging.exception('Expected') +else: + assert False + + +print("Example 7") +class TraceMeta(type): + def __new__(meta, name, bases, class_dict): + klass = super().__new__(meta, name, bases, class_dict) + + for key in dir(klass): + if key in IGNORE_METHODS: + continue + + value = getattr(klass, key) + if not isinstance(value, TRACE_TYPES): + continue + + wrapped = trace_func(value) + setattr(klass, key, wrapped) + + return klass + + +class OtherMeta(TraceMeta): + pass + +class SimpleDict(dict, metaclass=OtherMeta): + pass + +class ChildTraceDict(SimpleDict, metaclass=TraceMeta): + pass + +trace_dict = ChildTraceDict([("hi", 1)]) +trace_dict["there"] = 2 +trace_dict["hi"] +try: + trace_dict["does not exist"] +except KeyError: + pass # Expected +else: + assert False + + +print("Example 8") +def my_class_decorator(klass): + klass.extra_param = "hello" + return klass + +@my_class_decorator +class MyClass: + pass + +print(MyClass) +print(MyClass.extra_param) + + +print("Example 9") +def trace(klass): + for key in dir(klass): + if key in IGNORE_METHODS: + continue + + value = getattr(klass, key) + if not isinstance(value, TRACE_TYPES): + continue + + wrapped = trace_func(value) + setattr(klass, key, wrapped) + + return klass + + +print("Example 10") +@trace +class DecoratedTraceDict(dict): + pass + +trace_dict = DecoratedTraceDict([("hi", 1)]) +trace_dict["there"] = 2 +trace_dict["hi"] +try: + trace_dict["does not exist"] +except KeyError: + pass # Expected +else: + assert False + + +print("Example 11") +class OtherMeta(type): + pass + +@trace +class HasMetaTraceDict(dict, metaclass=OtherMeta): + pass + +trace_dict = HasMetaTraceDict([("hi", 1)]) +trace_dict["there"] = 2 +trace_dict["hi"] +try: + trace_dict["does not exist"] +except KeyError: + pass # Expected +else: + assert False diff --git a/example_code/item_067.py b/example_code/item_067.py new file mode 100755 index 0000000..d1e634b --- /dev/null +++ b/example_code/item_067.py @@ -0,0 +1,188 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import subprocess + +# Enable these lines to make this example work on Windows +# import os +# os.environ['COMSPEC'] = 'powershell' +result = subprocess.run( + ["echo", "Hello from the child!"], + capture_output=True, + # Enable this line to make this example work on Windows + # shell=True, + encoding="utf-8", +) + +result.check_returncode() # No exception means it exited cleanly +print(result.stdout) + + +print("Example 2") +# Use this line instead to make this example work on Windows +# proc = subprocess.Popen(['sleep', '1'], shell=True) +proc = subprocess.Popen(["sleep", "1"]) +while proc.poll() is None: + print("Working...") + # Some time-consuming work here + import time + + time.sleep(0.3) + +print("Exit status", proc.poll()) + + +print("Example 3") +import time + +start = time.perf_counter() +sleep_procs = [] +for _ in range(10): + # Use this line instead to make this example work on Windows + # proc = subprocess.Popen(['sleep', '1'], shell=True) + proc = subprocess.Popen(["sleep", "1"]) + sleep_procs.append(proc) + + +print("Example 4") +for proc in sleep_procs: + proc.communicate() + +end = time.perf_counter() +delta = end - start +print(f"Finished in {delta:.3} seconds") + + +print("Example 5") +import os + +# On Windows, after installing OpenSSL, you may need to +# alias it in your PowerShell path with a command like: +# $env:path = $env:path + ";C:\Program Files\OpenSSL-Win64\bin" + +def run_encrypt(data): + env = os.environ.copy() + env["password"] = "zf7ShyBhZOraQDdE/FiZpm/m/8f9X+M1" + proc = subprocess.Popen( + ["openssl", "enc", "-des3", "-pass", "env:password"], + env=env, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + proc.stdin.write(data) + proc.stdin.flush() # Ensure that the child gets input + return proc + + +print("Example 6") +procs = [] +for _ in range(3): + data = os.urandom(10) + proc = run_encrypt(data) + procs.append(proc) + + +print("Example 7") +for proc in procs: + out, _ = proc.communicate() + print(out[-10:]) + + +print("Example 8") +def run_hash(input_stdin): + return subprocess.Popen( + ["openssl", "dgst", "-whirlpool", "-binary"], + stdin=input_stdin, + stdout=subprocess.PIPE, + ) + + +print("Example 9") +encrypt_procs = [] +hash_procs = [] +for _ in range(3): + data = os.urandom(100) + + encrypt_proc = run_encrypt(data) + encrypt_procs.append(encrypt_proc) + + hash_proc = run_hash(encrypt_proc.stdout) + hash_procs.append(hash_proc) + + # Ensure that the child consumes the input stream and + # the communicate() method doesn't inadvertently steal + # input from the child. Also lets SIGPIPE propagate to + # the upstream process if the downstream process dies. + encrypt_proc.stdout.close() + encrypt_proc.stdout = None + + +print("Example 10") +for proc in encrypt_procs: + proc.communicate() + assert proc.returncode == 0 + +for proc in hash_procs: + out, _ = proc.communicate() + print(out[-10:]) + assert proc.returncode == 0 + + +print("Example 11") +# Use this line instead to make this example work on Windows +# proc = subprocess.Popen(['sleep', '10'], shell=True) +proc = subprocess.Popen(["sleep", "10"]) +try: + proc.communicate(timeout=0.1) +except subprocess.TimeoutExpired: + proc.terminate() + proc.wait() + +print("Exit status", proc.poll()) diff --git a/example_code/item_068.py b/example_code/item_068.py new file mode 100755 index 0000000..77d3641 --- /dev/null +++ b/example_code/item_068.py @@ -0,0 +1,146 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def factorize(number): + for i in range(1, number + 1): + if number % i == 0: + yield i + + +print("Example 2") +import time + +numbers = [7775876, 6694411, 5038540, 5426782, + 9934740, 9168996, 5271226, 8288002, + 9403196, 6678888, 6776096, 9582542, + 7107467, 9633726, 5747908, 7613918] +start = time.perf_counter() + +for number in numbers: + list(factorize(number)) + +end = time.perf_counter() +delta = end - start +print(f"Took {delta:.3f} seconds") + + +print("Example 3") +from threading import Thread + +class FactorizeThread(Thread): + def __init__(self, number): + super().__init__() + self.number = number + + def run(self): + self.factors = list(factorize(self.number)) + + +print("Example 4") +start = time.perf_counter() + +threads = [] +for number in numbers: + thread = FactorizeThread(number) + thread.start() + threads.append(thread) + + +print("Example 5") +for thread in threads: + thread.join() + +end = time.perf_counter() +delta = end - start +print(f"Took {delta:.3f} seconds") + + +print("Example 6") +import select +import socket + +def slow_systemcall(): + select.select([socket.socket()], [], [], 0.1) + + +print("Example 7") +start = time.perf_counter() + +for _ in range(5): + slow_systemcall() + +end = time.perf_counter() +delta = end - start +print(f"Took {delta:.3f} seconds") + + +print("Example 8") +start = time.perf_counter() + +threads = [] +for _ in range(5): + thread = Thread(target=slow_systemcall) + thread.start() + threads.append(thread) + + +print("Example 9") +def compute_helicopter_location(index): + pass + +for i in range(5): + compute_helicopter_location(i) + +for thread in threads: + thread.join() + +end = time.perf_counter() +delta = end - start +print(f"Took {delta:.3f} seconds") diff --git a/example_code/item_069.py b/example_code/item_069.py new file mode 100755 index 0000000..b98fab1 --- /dev/null +++ b/example_code/item_069.py @@ -0,0 +1,156 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +counter = 0 + +def read_sensor(sensor_index): + # Returns sensor data or raises an exception + # Nothing actually happens here, but this is where + # the blocking I/O would go. + pass + +def get_offset(data): + # Always returns 1 or greater + return 1 + +def worker(sensor_index, how_many): + global counter + # I have a barrier in here so the workers synchronize + # when they start counting, otherwise it's hard to get a race + # because the overhead of starting a thread is high. + BARRIER.wait() + for _ in range(how_many): + data = read_sensor(sensor_index) + # Note that the value passed to += must be a function call or other + # non-trivial expression in order to cause the CPython eval loop to + # check whether it should release the GIL. This is a side-effect of + # an optimization. See https://github.com/python/cpython/commit/4958f5d69dd2bf86866c43491caf72f774ddec97 for details. + counter += get_offset(data) + + +print("Example 2") +from threading import Thread + +how_many = 10**6 +sensor_count = 4 + +from threading import Barrier + +BARRIER = Barrier(sensor_count) + +threads = [] +for i in range(sensor_count): + thread = Thread(target=worker, args=(i, how_many)) + threads.append(thread) + thread.start() + +for thread in threads: + thread.join() + +expected = how_many * sensor_count +print(f"Counter should be {expected}, got {counter}") + + +print("Example 3") +data = None +counter += get_offset(data) + + +print("Example 4") +value = counter +delta = get_offset(data) +result = value + delta +counter = result + + +print("Example 5") +data_a = None +data_b = None +# Running in Thread A +value_a = counter +delta_a = get_offset(data_a) +# Context switch to Thread B +value_b = counter +delta_b = get_offset(data_b) +result_b = value_b + delta_b +counter = result_b +# Context switch back to Thread A +result_a = value_a + delta_a +counter = result_a + + +print("Example 6") +from threading import Lock + +counter = 0 +counter_lock = Lock() + +def locking_worker(sensor_index, how_many): + global counter + BARRIER.wait() + for _ in range(how_many): + data = read_sensor(sensor_index) + with counter_lock: # Added + counter += get_offset(data) + + +print("Example 7") +BARRIER = Barrier(sensor_count) + +for i in range(sensor_count): + thread = Thread(target=locking_worker, args=(i, how_many)) + threads.append(thread) + thread.start() + +for thread in threads: + thread.join() + +expected = how_many * sensor_count +print(f"Counter should be {expected}, got {counter}") diff --git a/example_code/item_070.py b/example_code/item_070.py new file mode 100755 index 0000000..352d3c7 --- /dev/null +++ b/example_code/item_070.py @@ -0,0 +1,382 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def download(item): + return item + +def resize(item): + return item + +def upload(item): + return item + + +print("Example 2") +from collections import deque +from threading import Lock + +class MyQueue: + def __init__(self): + self.items = deque() + self.lock = Lock() + + +print("Example 3") + def put(self, item): + with self.lock: + self.items.append(item) + + +print("Example 4") + def get(self): + with self.lock: + return self.items.popleft() + + +print("Example 5") +from threading import Thread +import time + +class Worker(Thread): + def __init__(self, func, in_queue, out_queue): + super().__init__() + self.func = func + self.in_queue = in_queue + self.out_queue = out_queue + self.polled_count = 0 + self.work_done = 0 + + +print("Example 6") + def run(self): + while True: + self.polled_count += 1 + try: + item = self.in_queue.get() + except IndexError: + time.sleep(0.01) # No work to do + except AttributeError: + # The magic exit signal to make this easy to show in + # example code, but don't use this in practice. + return + else: + result = self.func(item) + self.out_queue.put(result) + self.work_done += 1 + + +print("Example 7") +download_queue = MyQueue() +resize_queue = MyQueue() +upload_queue = MyQueue() +done_queue = MyQueue() +threads = [ + Worker(download, download_queue, resize_queue), + Worker(resize, resize_queue, upload_queue), + Worker(upload, upload_queue, done_queue), +] + + +print("Example 8") +for thread in threads: + thread.start() + +for _ in range(1000): + download_queue.put(object()) + + +print("Example 9") +while len(done_queue.items) < 1000: + # Do something useful while waiting + time.sleep(0.1) +# Stop all the threads by causing an exception in their +# run methods. +for thread in threads: + thread.in_queue = None + thread.join() + + +print("Example 10") +processed = len(done_queue.items) +polled = sum(t.polled_count for t in threads) +print(f"Processed {processed} items after " f"polling {polled} times") + + +print("Example 11") +from queue import Queue + +my_queue = Queue() + +def consumer(): + print("Consumer waiting") + my_queue.get() # Runs after put() below + print("Consumer done") + +thread = Thread(target=consumer) +thread.start() + + +print("Example 12") +print("Producer putting") +my_queue.put(object()) # Runs before get() above +print("Producer done") +thread.join() + + +print("Example 13") +my_queue = Queue(1) # Buffer size of 1 + +def consumer(): + time.sleep(0.1) # Wait + my_queue.get() # Runs second + print("Consumer got 1") + my_queue.get() # Runs fourth + print("Consumer got 2") + print("Consumer done") + +thread = Thread(target=consumer) +thread.start() + + +print("Example 14") +my_queue.put(object()) # Runs first +print("Producer put 1") +my_queue.put(object()) # Runs third +print("Producer put 2") +print("Producer done") +thread.join() + + +print("Example 15") +in_queue = Queue() + +def consumer(): + print("Consumer waiting") + work = in_queue.get() # Runs second + print("Consumer working") + # Doing work + print("Consumer done") + in_queue.task_done() # Runs third + +thread = Thread(target=consumer) +thread.start() + + +print("Example 16") +print("Producer putting") +in_queue.put(object()) # Runs first +print("Producer waiting") +in_queue.join() # Runs fourth +print("Producer done") +thread.join() + + +print("Example 17") +from queue import ShutDown + +my_queue2 = Queue() + +def consumer(): + while True: + try: + item = my_queue2.get() + except ShutDown: + print("Terminating!") + return + else: + print("Got item", item) + my_queue2.task_done() + +thread = Thread(target=consumer) +my_queue2.put(1) +my_queue2.put(2) +my_queue2.put(3) +my_queue2.shutdown() + +thread.start() + +my_queue2.join() +thread.join() +print("Done") + + +print("Example 18") +class StoppableWorker(Thread): + def __init__(self, func, in_queue, out_queue): + super().__init__() + self.func = func + self.in_queue = in_queue + self.out_queue = out_queue + + def run(self): + while True: + try: + item = self.in_queue.get() + except ShutDown: + return + else: + result = self.func(item) + self.out_queue.put(result) + self.in_queue.task_done() + + +print("Example 19") +download_queue = Queue() +resize_queue = Queue(100) +upload_queue = Queue(100) +done_queue = Queue() + +threads = [ + StoppableWorker(download, download_queue, resize_queue), + StoppableWorker(resize, resize_queue, upload_queue), + StoppableWorker(upload, upload_queue, done_queue), +] + +for thread in threads: + thread.start() + + +print("Example 20") +for _ in range(1000): + download_queue.put(object()) + + +print("Example 21") +download_queue.shutdown() +download_queue.join() + +resize_queue.shutdown() +resize_queue.join() + +upload_queue.shutdown() +upload_queue.join() + + +print("Example 22") +done_queue.shutdown() + +counter = 0 + +while True: + try: + item = done_queue.get() + except ShutDown: + break + else: + # Process the item + done_queue.task_done() + counter += 1 + +done_queue.join() + +for thread in threads: + thread.join() + +print(counter, "items finished") + + +print("Example 23") +def start_threads(count, *args): + threads = [StoppableWorker(*args) for _ in range(count)] + for thread in threads: + thread.start() + return threads + +def drain_queue(input_queue): + input_queue.shutdown() + + counter = 0 + + while True: + try: + item = input_queue.get() + except ShutDown: + break + else: + input_queue.task_done() + counter += 1 + + input_queue.join() + + return counter + + +print("Example 24") +download_queue = Queue() +resize_queue = Queue(100) +upload_queue = Queue(100) +done_queue = Queue() + +threads = ( + start_threads(3, download, download_queue, resize_queue) + + start_threads(4, resize, resize_queue, upload_queue) + + start_threads(5, upload, upload_queue, done_queue) +) + + +print("Example 25") +for _ in range(2000): + download_queue.put(object()) + +download_queue.shutdown() +download_queue.join() + +resize_queue.shutdown() +resize_queue.join() + +upload_queue.shutdown() +upload_queue.join() + +counter = drain_queue(done_queue) + +for thread in threads: + thread.join() + +print(counter, "items finished") diff --git a/example_code/item_071.py b/example_code/item_071.py new file mode 100755 index 0000000..6ff868d --- /dev/null +++ b/example_code/item_071.py @@ -0,0 +1,237 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +ALIVE = "*" +EMPTY = "-" + + +print("Example 2") +class Grid: + def __init__(self, height, width): + self.height = height + self.width = width + self.rows = [] + for _ in range(self.height): + self.rows.append([EMPTY] * self.width) + + def get(self, y, x): + return self.rows[y % self.height][x % self.width] + + def set(self, y, x, state): + self.rows[y % self.height][x % self.width] = state + + def __str__(self): + output = "" + for row in self.rows: + for cell in row: + output += cell + output += "\n" + return output + + +print("Example 3") +grid = Grid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) +print(grid) + + +print("Example 4") +def count_neighbors(y, x, get_cell): + n_ = get_cell(y - 1, x + 0) # North + ne = get_cell(y - 1, x + 1) # Northeast + e_ = get_cell(y + 0, x + 1) # East + se = get_cell(y + 1, x + 1) # Southeast + s_ = get_cell(y + 1, x + 0) # South + sw = get_cell(y + 1, x - 1) # Southwest + w_ = get_cell(y + 0, x - 1) # West + nw = get_cell(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + + +alive = {(9, 5), (9, 6)} +seen = set() + +def fake_get(y, x): + position = (y, x) + seen.add(position) + return ALIVE if position in alive else EMPTY + +count = count_neighbors(10, 5, fake_get) +assert count == 2 + +expected_seen = { + (9, 5), + (9, 6), + (10, 6), + (11, 6), + (11, 5), + (11, 4), + (10, 4), + (9, 4), +} +assert seen == expected_seen + + +print("Example 5") +def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY # Die: Too few + elif neighbors > 3: + return EMPTY # Die: Too many + else: + if neighbors == 3: + return ALIVE # Regenerate + return state + +assert game_logic(ALIVE, 0) == EMPTY +assert game_logic(ALIVE, 1) == EMPTY +assert game_logic(ALIVE, 2) == ALIVE +assert game_logic(ALIVE, 3) == ALIVE +assert game_logic(ALIVE, 4) == EMPTY +assert game_logic(EMPTY, 0) == EMPTY +assert game_logic(EMPTY, 1) == EMPTY +assert game_logic(EMPTY, 2) == EMPTY +assert game_logic(EMPTY, 3) == ALIVE +assert game_logic(EMPTY, 4) == EMPTY + + +print("Example 6") +def step_cell(y, x, get_cell, set_cell): + state = get_cell(y, x) + neighbors = count_neighbors(y, x, get_cell) + next_state = game_logic(state, neighbors) + set_cell(y, x, next_state) + + +alive = {(10, 5), (9, 5), (9, 6)} +new_state = None + +def fake_get(y, x): + return ALIVE if (y, x) in alive else EMPTY + +def fake_set(y, x, state): + global new_state + new_state = state + +# Stay alive +step_cell(10, 5, fake_get, fake_set) +assert new_state == ALIVE + +# Stay dead +alive.remove((10, 5)) +step_cell(10, 5, fake_get, fake_set) +assert new_state == EMPTY + +# Regenerate +alive.add((10, 6)) +step_cell(10, 5, fake_get, fake_set) +assert new_state == ALIVE + + +print("Example 7") +def simulate(grid): + next_grid = Grid(grid.height, grid.width) + for y in range(grid.height): + for x in range(grid.width): + step_cell(y, x, grid.get, next_grid.set) + return next_grid + + +print("Example 8") +class ColumnPrinter: + def __init__(self): + self.columns = [] + + def append(self, data): + self.columns.append(data) + + def __str__(self): + row_count = 1 + for data in self.columns: + row_count = max(row_count, len(data.splitlines()) + 1) + + rows = [""] * row_count + for j in range(row_count): + for i, data in enumerate(self.columns): + line = data.splitlines()[max(0, j - 1)] + if j == 0: + padding = " " * (len(line) // 2) + rows[j] += padding + str(i) + padding + else: + rows[j] += line + + if (i + 1) < len(self.columns): + rows[j] += " | " + + return "\n".join(rows) + + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = simulate(grid) + +print(columns) + + +print("Example 9") +def game_logic(state, neighbors): + # Do some blocking input/output in here: + data = my_socket.recv(100) diff --git a/example_code/item_072.py b/example_code/item_072.py new file mode 100755 index 0000000..a0e57bb --- /dev/null +++ b/example_code/item_072.py @@ -0,0 +1,216 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +from threading import Lock + +ALIVE = "*" +EMPTY = "-" + +class Grid: + def __init__(self, height, width): + self.height = height + self.width = width + self.rows = [] + for _ in range(self.height): + self.rows.append([EMPTY] * self.width) + + def get(self, y, x): + return self.rows[y % self.height][x % self.width] + + def set(self, y, x, state): + self.rows[y % self.height][x % self.width] = state + + def __str__(self): + output = "" + for row in self.rows: + for cell in row: + output += cell + output += "\n" + return output + + +class LockingGrid(Grid): + def __init__(self, height, width): + super().__init__(height, width) + self.lock = Lock() + + def __str__(self): + with self.lock: + return super().__str__() + + def get(self, y, x): + with self.lock: + return super().get(y, x) + + def set(self, y, x, state): + with self.lock: + return super().set(y, x, state) + + +print("Example 2") +from threading import Thread + +def count_neighbors(y, x, get_cell): + n_ = get_cell(y - 1, x + 0) # North + ne = get_cell(y - 1, x + 1) # Northeast + e_ = get_cell(y + 0, x + 1) # East + se = get_cell(y + 1, x + 1) # Southeast + s_ = get_cell(y + 1, x + 0) # South + sw = get_cell(y + 1, x - 1) # Southwest + w_ = get_cell(y + 0, x - 1) # West + nw = get_cell(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +def game_logic(state, neighbors): + # This version of the function is just to illustrate the point + # that I/O is possible, but for example code we'll simply run + # the normal game logic (below) so it's easier to understand. + # Do some blocking input/output in here: + data = my_socket.recv(100) + +def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY + elif neighbors > 3: + return EMPTY + else: + if neighbors == 3: + return ALIVE + return state + +def step_cell(y, x, get_cell, set_cell): + state = get_cell(y, x) + neighbors = count_neighbors(y, x, get_cell) + next_state = game_logic(state, neighbors) + set_cell(y, x, next_state) + +def simulate_threaded(grid): + next_grid = LockingGrid(grid.height, grid.width) + + threads = [] + for y in range(grid.height): + for x in range(grid.width): + args = (y, x, grid.get, next_grid.set) + thread = Thread(target=step_cell, args=args) + thread.start() # Fan-out + threads.append(thread) + + for thread in threads: + thread.join() # Fan-in + + return next_grid + + +print("Example 3") +class ColumnPrinter: + def __init__(self): + self.columns = [] + + def append(self, data): + self.columns.append(data) + + def __str__(self): + row_count = 1 + for data in self.columns: + row_count = max(row_count, len(data.splitlines()) + 1) + + rows = [""] * row_count + for j in range(row_count): + for i, data in enumerate(self.columns): + line = data.splitlines()[max(0, j - 1)] + if j == 0: + padding = " " * (len(line) // 2) + rows[j] += padding + str(i) + padding + else: + rows[j] += line + + if (i + 1) < len(self.columns): + rows[j] += " | " + + return "\n".join(rows) + + +grid = LockingGrid(5, 9) # Changed +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = simulate_threaded(grid) # Changed + +print(columns) + + +print("Example 4") +def game_logic(state, neighbors): + raise OSError("Problem with I/O") + + +print("Example 5") +import contextlib +import io + +fake_stderr = io.StringIO() +with contextlib.redirect_stderr(fake_stderr): + thread = Thread(target=game_logic, args=(ALIVE, 3)) + thread.start() + thread.join() + +print(fake_stderr.getvalue()) diff --git a/example_code/item_073.py b/example_code/item_073.py new file mode 100755 index 0000000..8e23346 --- /dev/null +++ b/example_code/item_073.py @@ -0,0 +1,413 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +from queue import Queue + +in_queue = Queue() +out_queue = Queue() + + +print("Example 2") +from threading import Thread + +from queue import ShutDown + + +class StoppableWorker(Thread): + def __init__(self, func, in_queue, out_queue, *args, **kwargs): + super().__init__(*args, **kwargs) + self.func = func + self.in_queue = in_queue + self.out_queue = out_queue + + def run(self): + while True: + try: + item = self.in_queue.get() + except ShutDown: + return + else: + result = self.func(item) + self.out_queue.put(result) + self.in_queue.task_done() + + +def game_logic(state, neighbors): + # Do some blocking input/output in here: + data = my_socket.recv(100) + +def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY + elif neighbors > 3: + return EMPTY + else: + if neighbors == 3: + return ALIVE + return state + +def game_logic_thread(item): + y, x, state, neighbors = item + try: + next_state = game_logic(state, neighbors) + except Exception as e: + next_state = e + return (y, x, next_state) + +# Start the threads upfront +threads = [] +for _ in range(5): + thread = StoppableWorker(game_logic_thread, in_queue, out_queue) + thread.start() + threads.append(thread) + + +print("Example 3") +ALIVE = "*" +EMPTY = "-" + +class SimulationError(Exception): + pass + +class Grid: + def __init__(self, height, width): + self.height = height + self.width = width + self.rows = [] + for _ in range(self.height): + self.rows.append([EMPTY] * self.width) + + def get(self, y, x): + return self.rows[y % self.height][x % self.width] + + def set(self, y, x, state): + self.rows[y % self.height][x % self.width] = state + + def __str__(self): + output = "" + for row in self.rows: + for cell in row: + output += cell + output += "\n" + return output + + +def count_neighbors(y, x, get_cell): + n_ = get_cell(y - 1, x + 0) # North + ne = get_cell(y - 1, x + 1) # Northeast + e_ = get_cell(y + 0, x + 1) # East + se = get_cell(y + 1, x + 1) # Southeast + s_ = get_cell(y + 1, x + 0) # South + sw = get_cell(y + 1, x - 1) # Southwest + w_ = get_cell(y + 0, x - 1) # West + nw = get_cell(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +def simulate_pipeline(grid, in_queue, out_queue): + for y in range(grid.height): + for x in range(grid.width): + state = grid.get(y, x) + neighbors = count_neighbors(y, x, grid.get) + in_queue.put((y, x, state, neighbors)) # Fan-out + + in_queue.join() + item_count = out_queue.qsize() + + next_grid = Grid(grid.height, grid.width) + for _ in range(item_count): + item = out_queue.get() # Fan-in + y, x, next_state = item + if isinstance(next_state, Exception): + raise SimulationError(y, x) from next_state + next_grid.set(y, x, next_state) + + return next_grid + + +print("Example 4") +try: + def game_logic(state, neighbors): + raise OSError("Problem with I/O in game_logic") + + simulate_pipeline(Grid(1, 1), in_queue, out_queue) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +# Restore the working version of this function +def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY + elif neighbors > 3: + return EMPTY + else: + if neighbors == 3: + return ALIVE + return state + +class ColumnPrinter: + def __init__(self): + self.columns = [] + + def append(self, data): + self.columns.append(data) + + def __str__(self): + row_count = 1 + for data in self.columns: + row_count = max(row_count, len(data.splitlines()) + 1) + + rows = [""] * row_count + for j in range(row_count): + for i, data in enumerate(self.columns): + line = data.splitlines()[max(0, j - 1)] + if j == 0: + padding = " " * (len(line) // 2) + rows[j] += padding + str(i) + padding + else: + rows[j] += line + + if (i + 1) < len(self.columns): + rows[j] += " | " + + return "\n".join(rows) + + +grid = Grid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = simulate_pipeline(grid, in_queue, out_queue) + +print(columns) + +in_queue.shutdown() +in_queue.join() + +for thread in threads: + thread.join() + + +print("Example 6") +def count_neighbors(y, x, get_cell): + # Do some blocking input/output in here: + data = my_socket.recv(100) + + +print("Example 7") +def count_neighbors(y, x, get_cell): + n_ = get_cell(y - 1, x + 0) # North + ne = get_cell(y - 1, x + 1) # Northeast + e_ = get_cell(y + 0, x + 1) # East + se = get_cell(y + 1, x + 1) # Southeast + s_ = get_cell(y + 1, x + 0) # South + sw = get_cell(y + 1, x - 1) # Southwest + w_ = get_cell(y + 0, x - 1) # West + nw = get_cell(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +def count_neighbors_thread(item): + y, x, state, get_cell = item + try: + neighbors = count_neighbors(y, x, get_cell) + except Exception as e: + neighbors = e + return (y, x, state, neighbors) + +def game_logic_thread(item): + y, x, state, neighbors = item + if isinstance(neighbors, Exception): + next_state = neighbors + else: + try: + next_state = game_logic(state, neighbors) + except Exception as e: + next_state = e + return (y, x, next_state) + +from threading import Lock + +class LockingGrid(Grid): + def __init__(self, height, width): + super().__init__(height, width) + self.lock = Lock() + + def __str__(self): + with self.lock: + return super().__str__() + + def get(self, y, x): + with self.lock: + return super().get(y, x) + + def set(self, y, x, state): + with self.lock: + return super().set(y, x, state) + + +print("Example 8") +in_queue = Queue() +logic_queue = Queue() +out_queue = Queue() + +threads = [] + +for _ in range(5): + thread = StoppableWorker( + count_neighbors_thread, in_queue, logic_queue + ) + thread.start() + threads.append(thread) + +for _ in range(5): + thread = StoppableWorker( + game_logic_thread, logic_queue, out_queue + ) + thread.start() + threads.append(thread) + + +print("Example 9") +def simulate_phased_pipeline(grid, in_queue, logic_queue, out_queue): + for y in range(grid.height): + for x in range(grid.width): + state = grid.get(y, x) + item = (y, x, state, grid.get) + in_queue.put(item) # Fan-out + + in_queue.join() + logic_queue.join() # Pipeline sequencing + item_count = out_queue.qsize() + + next_grid = LockingGrid(grid.height, grid.width) + for _ in range(item_count): + y, x, next_state = out_queue.get() # Fan-in + if isinstance(next_state, Exception): + raise SimulationError(y, x) from next_state + next_grid.set(y, x, next_state) + + return next_grid + + +print("Example 10") +grid = LockingGrid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = simulate_phased_pipeline( + grid, in_queue, logic_queue, out_queue + ) + +print(columns) + +in_queue.shutdown() +in_queue.join() + +logic_queue.shutdown() +logic_queue.join() + +for thread in threads: + thread.join() + + +print("Example 11") +# Make sure exception propagation works as expected +def count_neighbors(*args): + raise OSError("Problem with I/O in count_neighbors") + +in_queue = Queue() +logic_queue = Queue() +out_queue = Queue() + +threads = [ + StoppableWorker( + count_neighbors_thread, in_queue, logic_queue, daemon=True + ), + StoppableWorker( + game_logic_thread, logic_queue, out_queue, daemon=True + ), +] + +for thread in threads: + thread.start() + +try: + simulate_phased_pipeline(grid, in_queue, logic_queue, out_queue) +except SimulationError: + pass # Expected +else: + assert False diff --git a/example_code/item_074.py b/example_code/item_074.py new file mode 100755 index 0000000..ec48443 --- /dev/null +++ b/example_code/item_074.py @@ -0,0 +1,210 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +ALIVE = "*" +EMPTY = "-" + +class Grid: + def __init__(self, height, width): + self.height = height + self.width = width + self.rows = [] + for _ in range(self.height): + self.rows.append([EMPTY] * self.width) + + def get(self, y, x): + return self.rows[y % self.height][x % self.width] + + def set(self, y, x, state): + self.rows[y % self.height][x % self.width] = state + + def __str__(self): + output = "" + for row in self.rows: + for cell in row: + output += cell + output += "\n" + return output + + +from threading import Lock + +class LockingGrid(Grid): + def __init__(self, height, width): + super().__init__(height, width) + self.lock = Lock() + + def __str__(self): + with self.lock: + return super().__str__() + + def get(self, y, x): + with self.lock: + return super().get(y, x) + + def set(self, y, x, state): + with self.lock: + return super().set(y, x, state) + + +def count_neighbors(y, x, get_cell): + n_ = get_cell(y - 1, x + 0) # North + ne = get_cell(y - 1, x + 1) # Northeast + e_ = get_cell(y + 0, x + 1) # East + se = get_cell(y + 1, x + 1) # Southeast + s_ = get_cell(y + 1, x + 0) # South + sw = get_cell(y + 1, x - 1) # Southwest + w_ = get_cell(y + 0, x - 1) # West + nw = get_cell(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +def game_logic(state, neighbors): + # Do some blocking input/output in here: + data = my_socket.recv(100) + +def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY + elif neighbors > 3: + return EMPTY + else: + if neighbors == 3: + return ALIVE + return state + +def step_cell(y, x, get_cell, set_cell): + state = get_cell(y, x) + neighbors = count_neighbors(y, x, get_cell) + next_state = game_logic(state, neighbors) + set_cell(y, x, next_state) + + +print("Example 2") +from concurrent.futures import ThreadPoolExecutor + +def simulate_pool(pool, grid): + next_grid = LockingGrid(grid.height, grid.width) + + futures = [] + for y in range(grid.height): + for x in range(grid.width): + args = (y, x, grid.get, next_grid.set) + future = pool.submit(step_cell, *args) # Fan-out + futures.append(future) + + for future in futures: + future.result() # Fan-in + + return next_grid + + +print("Example 3") +class ColumnPrinter: + def __init__(self): + self.columns = [] + + def append(self, data): + self.columns.append(data) + + def __str__(self): + row_count = 1 + for data in self.columns: + row_count = max(row_count, len(data.splitlines()) + 1) + + rows = [""] * row_count + for j in range(row_count): + for i, data in enumerate(self.columns): + line = data.splitlines()[max(0, j - 1)] + if j == 0: + padding = " " * (len(line) // 2) + rows[j] += padding + str(i) + padding + else: + rows[j] += line + + if (i + 1) < len(self.columns): + rows[j] += " | " + + return "\n".join(rows) + + +grid = LockingGrid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +with ThreadPoolExecutor(max_workers=10) as pool: + for i in range(5): + columns.append(str(grid)) + grid = simulate_pool(pool, grid) + +print(columns) + + +print("Example 4") +try: + def game_logic(state, neighbors): + raise OSError("Problem with I/O") + + with ThreadPoolExecutor(max_workers=10) as pool: + task = pool.submit(game_logic, ALIVE, 3) + task.result() +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_075.py b/example_code/item_075.py new file mode 100755 index 0000000..f92fbb6 --- /dev/null +++ b/example_code/item_075.py @@ -0,0 +1,233 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +ALIVE = "*" +EMPTY = "-" + +class Grid: + def __init__(self, height, width): + self.height = height + self.width = width + self.rows = [] + for _ in range(self.height): + self.rows.append([EMPTY] * self.width) + + def get(self, y, x): + return self.rows[y % self.height][x % self.width] + + def set(self, y, x, state): + self.rows[y % self.height][x % self.width] = state + + def __str__(self): + output = "" + for row in self.rows: + for cell in row: + output += cell + output += "\n" + return output + + +def count_neighbors(y, x, get_cell): + n_ = get_cell(y - 1, x + 0) # North + ne = get_cell(y - 1, x + 1) # Northeast + e_ = get_cell(y + 0, x + 1) # East + se = get_cell(y + 1, x + 1) # Southeast + s_ = get_cell(y + 1, x + 0) # South + sw = get_cell(y + 1, x - 1) # Southwest + w_ = get_cell(y + 0, x - 1) # West + nw = get_cell(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +async def game_logic(state, neighbors): + # Do some input/output in here: + data = await my_socket.read(50) + + +async def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY + elif neighbors > 3: + return EMPTY + else: + if neighbors == 3: + return ALIVE + return state + + +print("Example 2") +async def step_cell(y, x, get_cell, set_cell): + state = get_cell(y, x) + neighbors = count_neighbors(y, x, get_cell) + next_state = await game_logic(state, neighbors) + set_cell(y, x, next_state) + + +print("Example 3") +import asyncio + +async def simulate(grid): + next_grid = Grid(grid.height, grid.width) + + tasks = [] + for y in range(grid.height): + for x in range(grid.width): + task = step_cell(y, x, grid.get, next_grid.set) # Fan-out + tasks.append(task) + + await asyncio.gather(*tasks) # Fan-in + + return next_grid + + +print("Example 4") +class ColumnPrinter: + def __init__(self): + self.columns = [] + + def append(self, data): + self.columns.append(data) + + def __str__(self): + row_count = 1 + for data in self.columns: + row_count = max(row_count, len(data.splitlines()) + 1) + + rows = [""] * row_count + for j in range(row_count): + for i, data in enumerate(self.columns): + line = data.splitlines()[max(0, j - 1)] + if j == 0: + padding = " " * (len(line) // 2) + rows[j] += padding + str(i) + padding + else: + rows[j] += line + + if (i + 1) < len(self.columns): + rows[j] += " | " + + return "\n".join(rows) + + +logging.getLogger().setLevel(logging.ERROR) + +grid = Grid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = asyncio.run(simulate(grid)) # Run the event loop + +print(columns) + +logging.getLogger().setLevel(logging.DEBUG) + + +print("Example 5") +async def count_neighbors(y, x, get_cell): + n_ = get_cell(y - 1, x + 0) # North + ne = get_cell(y - 1, x + 1) # Northeast + e_ = get_cell(y + 0, x + 1) # East + se = get_cell(y + 1, x + 1) # Southeast + s_ = get_cell(y + 1, x + 0) # South + sw = get_cell(y + 1, x - 1) # Southwest + w_ = get_cell(y + 0, x - 1) # West + nw = get_cell(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +async def step_cell(y, x, get_cell, set_cell): + state = get_cell(y, x) + neighbors = await count_neighbors(y, x, get_cell) + next_state = await game_logic(state, neighbors) + set_cell(y, x, next_state) + +async def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY + elif neighbors > 3: + return EMPTY + else: + if neighbors == 3: + return ALIVE + return state + +logging.getLogger().setLevel(logging.ERROR) + +grid = Grid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = asyncio.run(simulate(grid)) + +print(columns) + +logging.getLogger().setLevel(logging.DEBUG) diff --git a/example_code/item_076.py b/example_code/item_076.py new file mode 100755 index 0000000..875a013 --- /dev/null +++ b/example_code/item_076.py @@ -0,0 +1,496 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class EOFError(Exception): + pass + +class Connection: + def __init__(self, connection): + self.connection = connection + self.file = connection.makefile("rb") + + def send(self, command): + line = command + "\n" + data = line.encode() + self.connection.send(data) + + def receive(self): + line = self.file.readline() + if not line: + raise EOFError("Connection closed") + return line[:-1].decode() + + +print("Example 2") +import random + +WARMER = "Warmer" +COLDER = "Colder" +SAME = "Same" +UNSURE = "Unsure" +CORRECT = "Correct" + +class UnknownCommandError(Exception): + pass + +class ServerSession(Connection): + def __init__(self, *args): + super().__init__(*args) + self.clear_state() + + +print("Example 3") + def loop(self): + while command := self.receive(): + match command.split(" "): + case "PARAMS", lower, upper: + self.set_params(lower, upper) + case ["NUMBER"]: + self.send_number() + case "REPORT", decision: + self.receive_report(decision) + case ["CLEAR"]: + self.clear_state() + case _: + raise UnknownCommandError(command) + + +print("Example 4") + def set_params(self, lower, upper): + self.clear_state() + self.lower = int(lower) + self.upper = int(upper) + + +print("Example 5") + def next_guess(self): + if self.secret is not None: + return self.secret + + while True: + guess = random.randint(self.lower, self.upper) + if guess not in self.guesses: + return guess + + def send_number(self): + guess = self.next_guess() + self.guesses.append(guess) + self.send(format(guess)) + + +print("Example 6") + def receive_report(self, decision): + last = self.guesses[-1] + if decision == CORRECT: + self.secret = last + + print(f"Server: {last} is {decision}") + + +print("Example 7") + def clear_state(self): + self.lower = None + self.upper = None + self.secret = None + self.guesses = [] + + +print("Example 8") +import contextlib + +@contextlib.contextmanager +def new_game(connection, lower, upper, secret): + print( + f"Guess a number between {lower} and {upper}!" + f" Shhhhh, it's {secret}." + ) + connection.send(f"PARAMS {lower} {upper}") + try: + yield ClientSession( + connection.send, + connection.receive, + secret, + ) + finally: + # Make it so the output printing matches what you expect + time.sleep(0.1) + connection.send("CLEAR") + + +print("Example 9") +import math + +class ClientSession: + def __init__(self, send, receive, secret): + self.send = send + self.receive = receive + self.secret = secret + self.last_distance = None + + +print("Example 10") + def request_number(self): + self.send("NUMBER") + data = self.receive() + return int(data) + + +print("Example 11") + def report_outcome(self, number): + new_distance = math.fabs(number - self.secret) + + if new_distance == 0: + decision = CORRECT + elif self.last_distance is None: + decision = UNSURE + elif new_distance < self.last_distance: + decision = WARMER + elif new_distance > self.last_distance: + decision = COLDER + else: + decision = SAME + + self.last_distance = new_distance + + self.send(f"REPORT {decision}") + return decision + + +print("Example 12") + def __iter__(self): + while True: + number = self.request_number() + decision = self.report_outcome(number) + yield number, decision + if decision == CORRECT: + return + + +print("Example 13") +import socket +from threading import Thread + +def handle_connection(connection): + with connection: + session = ServerSession(connection) + try: + session.loop() + except EOFError: + pass + +def run_server(address): + with socket.socket() as listener: + # Allow the port to be reused + listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + listener.bind(address) + listener.listen() + while True: + connection, _ = listener.accept() + thread = Thread( + target=handle_connection, + args=(connection,), + daemon=True, + ) + thread.start() + + +print("Example 14") +def run_client(address): + with socket.create_connection(address) as server_sock: + server = Connection(server_sock) + + with new_game(server, 1, 5, 3) as session: + results = [outcome for outcome in session] + + with new_game(server, 10, 15, 12) as session: + for outcome in session: + results.append(outcome) + + with new_game(server, 1, 3, 2) as session: + it = iter(session) + while True: + try: + outcome = next(it) + except StopIteration: + break + else: + results.append(outcome) + + return results + + +print("Example 15") +def main(): + address = ("127.0.0.1", 1234) + server_thread = Thread( + target=run_server, args=(address,), daemon=True + ) + server_thread.start() + + results = run_client(address) + for number, outcome in results: + print(f"Client: {number} is {outcome}") + +main() + + +print("Example 16") +class AsyncConnection: + def __init__(self, reader, writer): # Changed + self.reader = reader # Changed + self.writer = writer # Changed + + async def send(self, command): + line = command + "\n" + data = line.encode() + self.writer.write(data) # Changed + await self.writer.drain() # Changed + + async def receive(self): + line = await self.reader.readline() # Changed + if not line: + raise EOFError("Connection closed") + return line[:-1].decode() + + +print("Example 17") +class AsyncServerSession(AsyncConnection): # Changed + def __init__(self, *args): + super().__init__(*args) + self.clear_state() + + +print("Example 18") + async def loop(self): # Changed + while command := await self.receive(): # Changed + match command.split(" "): + case "PARAMS", lower, upper: + self.set_params(lower, upper) + case ["NUMBER"]: + await self.send_number() # Changed + case "REPORT", decision: + self.receive_report(decision) + case ["CLEAR"]: + self.clear_state() + case _: + raise UnknownCommandError(command) + + +print("Example 19") + def set_params(self, lower, upper): + self.clear_state() + self.lower = int(lower) + self.upper = int(upper) + + +print("Example 20") + def next_guess(self): + if self.secret is not None: + return self.secret + + while True: + guess = random.randint(self.lower, self.upper) + if guess not in self.guesses: + return guess + async def send_number(self): # Changed + guess = self.next_guess() + self.guesses.append(guess) + await self.send(format(guess)) # Changed + + +print("Example 21") + def receive_report(self, decision): + last = self.guesses[-1] + if decision == CORRECT: + self.secret = last + + print(f"Server: {last} is {decision}") + + def clear_state(self): + self.lower = None + self.upper = None + self.secret = None + self.guesses = [] + + +print("Example 22") +@contextlib.asynccontextmanager # Changed +async def new_async_game(connection, lower, upper, secret): # Changed + print( + f"Guess a number between {lower} and {upper}!" + f" Shhhhh, it's {secret}." + ) + await connection.send(f"PARAMS {lower} {upper}") # Changed + try: + yield AsyncClientSession( + connection.send, + connection.receive, + secret, + ) + finally: + # Make it so the output printing is in + # the same order as the threaded version. + await asyncio.sleep(0.1) + await connection.send("CLEAR") # Changed + + +print("Example 23") +class AsyncClientSession: + def __init__(self, send, receive, secret): + self.send = send + self.receive = receive + self.secret = secret + self.last_distance = None + + +print("Example 24") + async def request_number(self): + await self.send("NUMBER") # Changed + data = await self.receive() # Changed + return int(data) + + +print("Example 25") + async def report_outcome(self, number): # Changed + new_distance = math.fabs(number - self.secret) + + if new_distance == 0: + decision = CORRECT + elif self.last_distance is None: + decision = UNSURE + elif new_distance < self.last_distance: + decision = WARMER + elif new_distance > self.last_distance: + decision = COLDER + else: + decision = SAME + + self.last_distance = new_distance + + await self.send(f"REPORT {decision}") # Changed + return decision + + +print("Example 26") + async def __aiter__(self): # Changed + while True: + number = await self.request_number() # Changed + decision = await self.report_outcome(number) # Changed + yield number, decision + if decision == CORRECT: + return + + +print("Example 27") +import asyncio + +async def handle_async_connection(reader, writer): + session = AsyncServerSession(reader, writer) + try: + await session.loop() + except EOFError: + pass + +async def run_async_server(address): + server = await asyncio.start_server( + handle_async_connection, *address + ) + async with server: + await server.serve_forever() + + +print("Example 28") +async def run_async_client(address): + # Wait for the server to listen before trying to connect + await asyncio.sleep(0.1) + + streams = await asyncio.open_connection(*address) # New + client = AsyncConnection(*streams) # New + + async with new_async_game(client, 1, 5, 3) as session: + results = [outcome async for outcome in session] + + async with new_async_game(client, 10, 15, 12) as session: + async for outcome in session: + results.append(outcome) + + async with new_async_game(client, 1, 3, 2) as session: + it = aiter(session) + while True: + try: + outcome = await anext(it) + except StopAsyncIteration: + break + else: + results.append(outcome) + + _, writer = streams # New + writer.close() # New + await writer.wait_closed() # New + + return results + + +print("Example 29") +async def main_async(): + address = ("127.0.0.1", 4321) + + server = run_async_server(address) + asyncio.create_task(server) + + results = await run_async_client(address) + for number, outcome in results: + print(f"Client: {number} is {outcome}") + +logging.getLogger().setLevel(logging.ERROR) + +asyncio.run(main_async()) + +logging.getLogger().setLevel(logging.DEBUG) diff --git a/example_code/item_077.py b/example_code/item_077.py new file mode 100755 index 0000000..66867e1 --- /dev/null +++ b/example_code/item_077.py @@ -0,0 +1,305 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class NoNewData(Exception): + pass + +def readline(handle): + offset = handle.tell() + handle.seek(0, 2) + length = handle.tell() + + if length == offset: + raise NoNewData + + handle.seek(offset, 0) + return handle.readline() + + +print("Example 2") +import time + +def tail_file(handle, interval, write_func): + while not handle.closed: + try: + line = readline(handle) + except NoNewData: + time.sleep(interval) + else: + write_func(line) + + +print("Example 3") +from threading import Lock, Thread + +def run_threads(handles, interval, output_path): + with open(output_path, "wb") as output: + lock = Lock() + + def write(data): + with lock: + output.write(data) + + threads = [] + for handle in handles: + args = (handle, interval, write) + thread = Thread(target=tail_file, args=args) + thread.start() + threads.append(thread) + + for thread in threads: + thread.join() + + +print("Example 4") +# This is all code to simulate the writers to the handles +import collections +import os +import random +import string +from tempfile import TemporaryDirectory + +def write_random_data(path, write_count, interval): + with open(path, "wb") as f: + for i in range(write_count): + time.sleep(random.random() * interval) + letters = random.choices(string.ascii_lowercase, k=10) + data = f'{path}-{i:02}-{"".join(letters)}\n' + f.write(data.encode()) + f.flush() + +def start_write_threads(directory, file_count): + paths = [] + for i in range(file_count): + path = os.path.join(directory, str(i)) + with open(path, "w"): + # Make sure the file at this path will exist when + # the reading thread tries to poll it. + pass + paths.append(path) + args = (path, 10, 0.1) + thread = Thread(target=write_random_data, args=args) + thread.start() + return paths + +def close_all(handles): + time.sleep(1) + for handle in handles: + handle.close() + +def setup(): + tmpdir = TemporaryDirectory() + input_paths = start_write_threads(tmpdir.name, 5) + + handles = [] + for path in input_paths: + handle = open(path, "rb") + handles.append(handle) + + Thread(target=close_all, args=(handles,)).start() + + output_path = os.path.join(tmpdir.name, "merged") + return tmpdir, input_paths, handles, output_path + + +print("Example 5") +def confirm_merge(input_paths, output_path): + found = collections.defaultdict(list) + with open(output_path, "rb") as f: + for line in f: + for path in input_paths: + if line.find(path.encode()) == 0: + found[path].append(line) + + expected = collections.defaultdict(list) + for path in input_paths: + with open(path, "rb") as f: + expected[path].extend(f.readlines()) + + for key, expected_lines in expected.items(): + found_lines = found[key] + assert ( + expected_lines == found_lines + ), f"{expected_lines!r} == {found_lines!r}" + +input_paths = ... +handles = ... +output_path = ... + +tmpdir, input_paths, handles, output_path = setup() + +run_threads(handles, 0.1, output_path) + +confirm_merge(input_paths, output_path) + +tmpdir.cleanup() + + +print("Example 6") +import asyncio + +# TODO: Verify this is no longer needed +# +# On Windows, a ProactorEventLoop can't be created within +# threads because it tries to register signal handlers. This +# is a work-around to always use the SelectorEventLoop policy +# instead. See: https://bugs.python.org/issue33792 +# policy = asyncio.get_event_loop_policy() +# policy._loop_factory = asyncio.SelectorEventLoop +async def run_tasks_mixed(handles, interval, output_path): + loop = asyncio.get_event_loop() + + output = await loop.run_in_executor(None, open, output_path, "wb") + try: + + async def write_async(data): + await loop.run_in_executor(None, output.write, data) + + def write(data): + coro = write_async(data) + future = asyncio.run_coroutine_threadsafe(coro, loop) + future.result() + + tasks = [] + for handle in handles: + task = loop.run_in_executor( + None, tail_file, handle, interval, write + ) + tasks.append(task) + + await asyncio.gather(*tasks) + finally: + await loop.run_in_executor(None, output.close) + + +print("Example 7") +input_paths = ... +handles = ... +output_path = ... + +tmpdir, input_paths, handles, output_path = setup() + +asyncio.run(run_tasks_mixed(handles, 0.1, output_path)) + +confirm_merge(input_paths, output_path) + +tmpdir.cleanup() + + +print("Example 8") +async def tail_async(handle, interval, write_func): + loop = asyncio.get_event_loop() + + while not handle.closed: + try: + line = await loop.run_in_executor(None, readline, handle) + except NoNewData: + await asyncio.sleep(interval) + else: + await write_func(line) + + +print("Example 9") +async def run_tasks(handles, interval, output_path): + loop = asyncio.get_event_loop() + + output = await loop.run_in_executor(None, open, output_path, "wb") + try: + + async def write_async(data): + await loop.run_in_executor(None, output.write, data) + + async with asyncio.TaskGroup() as group: + for handle in handles: + group.create_task( + tail_async(handle, interval, write_async) + ) + finally: + await loop.run_in_executor(None, output.close) + + +print("Example 10") +input_paths = ... +handles = ... +output_path = ... + +tmpdir, input_paths, handles, output_path = setup() + +asyncio.run(run_tasks(handles, 0.1, output_path)) + +confirm_merge(input_paths, output_path) + +tmpdir.cleanup() + + +print("Example 11") +def tail_file(handle, interval, write_func): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + async def write_async(data): + await loop.run_in_executor(None, write_func, data) + + coro = tail_async(handle, interval, write_async) + loop.run_until_complete(coro) + + +print("Example 12") +input_paths = ... +handles = ... +output_path = ... + +tmpdir, input_paths, handles, output_path = setup() + +run_threads(handles, 0.1, output_path) + +confirm_merge(input_paths, output_path) + +tmpdir.cleanup() diff --git a/example_code/item_078.py b/example_code/item_078.py new file mode 100755 index 0000000..e0f2b30 --- /dev/null +++ b/example_code/item_078.py @@ -0,0 +1,262 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import asyncio + +async def run_tasks(handles, interval, output_path): + loop = asyncio.get_event_loop() + + output = await loop.run_in_executor(None, open, output_path, "wb") + try: + + async def write_async(data): + await loop.run_in_executor(None, output.write, data) + + async with asyncio.TaskGroup() as group: + for handle in handles: + group.create_task( + tail_async(handle, interval, write_async) + ) + finally: + await loop.run_in_executor(None, output.close) + + +print("Example 2") +async def run_tasks_simpler(handles, interval, output_path): + with open(output_path, "wb") as output: # Changed + + async def write_async(data): + output.write(data) # Changed + + async with asyncio.TaskGroup() as group: + for handle in handles: + group.create_task( + tail_async(handle, interval, write_async) + ) + + +print("Example 3") +import time + +async def slow_coroutine(): + time.sleep(0.5) # Simulating slow I/O + +asyncio.run(slow_coroutine(), debug=True) + + +print("Example 4") +from threading import Thread + +class WriteThread(Thread): + def __init__(self, output_path): + super().__init__() + self.output_path = output_path + self.output = None + self.loop = asyncio.new_event_loop() + + def run(self): + asyncio.set_event_loop(self.loop) + with open(self.output_path, "wb") as self.output: + self.loop.run_forever() + + # Run one final round of callbacks so the await on + # stop() in another event loop will be resolved. + self.loop.run_until_complete(asyncio.sleep(0)) + + +print("Example 5") + async def real_write(self, data): + self.output.write(data) + + async def write(self, data): + coro = self.real_write(data) + future = asyncio.run_coroutine_threadsafe( + coro, self.loop) + await asyncio.wrap_future(future) + + +print("Example 6") + async def real_stop(self): + self.loop.stop() + + async def stop(self): + coro = self.real_stop() + future = asyncio.run_coroutine_threadsafe( + coro, self.loop) + await asyncio.wrap_future(future) + + +print("Example 7") + async def __aenter__(self): + loop = asyncio.get_event_loop() + await loop.run_in_executor(None, self.start) + return self + + async def __aexit__(self, *_): + await self.stop() + + +print("Example 8") +class NoNewData(Exception): + pass + +def readline(handle): + offset = handle.tell() + handle.seek(0, 2) + length = handle.tell() + + if length == offset: + raise NoNewData + + handle.seek(offset, 0) + return handle.readline() + +async def tail_async(handle, interval, write_func): + loop = asyncio.get_event_loop() + + while not handle.closed: + try: + line = await loop.run_in_executor(None, readline, handle) + except NoNewData: + await asyncio.sleep(interval) + else: + await write_func(line) + +async def run_fully_async(handles, interval, output_path): + async with ( + WriteThread(output_path) as output, + asyncio.TaskGroup() as group, + ): + for handle in handles: + group.create_task( + tail_async(handle, interval, output.write) + ) + + +print("Example 9") +# This is all code to simulate the writers to the handles +import collections +import os +import random +import string +from tempfile import TemporaryDirectory + +def write_random_data(path, write_count, interval): + with open(path, "wb") as f: + for i in range(write_count): + time.sleep(random.random() * interval) + letters = random.choices(string.ascii_lowercase, k=10) + data = f'{path}-{i:02}-{"".join(letters)}\n' + f.write(data.encode()) + f.flush() + +def start_write_threads(directory, file_count): + paths = [] + for i in range(file_count): + path = os.path.join(directory, str(i)) + with open(path, "w"): + # Make sure the file at this path will exist when + # the reading thread tries to poll it. + pass + paths.append(path) + args = (path, 10, 0.1) + thread = Thread(target=write_random_data, args=args) + thread.start() + return paths + +def close_all(handles): + time.sleep(1) + for handle in handles: + handle.close() + +def setup(): + tmpdir = TemporaryDirectory() + input_paths = start_write_threads(tmpdir.name, 5) + + handles = [] + for path in input_paths: + handle = open(path, "rb") + handles.append(handle) + + Thread(target=close_all, args=(handles,)).start() + + output_path = os.path.join(tmpdir.name, "merged") + return tmpdir, input_paths, handles, output_path + + +print("Example 10") +def confirm_merge(input_paths, output_path): + found = collections.defaultdict(list) + with open(output_path, "rb") as f: + for line in f: + for path in input_paths: + if line.find(path.encode()) == 0: + found[path].append(line) + + expected = collections.defaultdict(list) + for path in input_paths: + with open(path, "rb") as f: + expected[path].extend(f.readlines()) + + for key, expected_lines in expected.items(): + found_lines = found[key] + assert expected_lines == found_lines + +input_paths = ... +handles = ... +output_path = ... + +tmpdir, input_paths, handles, output_path = setup() + +asyncio.run(run_fully_async(handles, 0.1, output_path)) + +confirm_merge(input_paths, output_path) + +tmpdir.cleanup() diff --git a/example_code/item_079/parallel/my_module.py b/example_code/item_079/parallel/my_module.py new file mode 100755 index 0000000..a61a84b --- /dev/null +++ b/example_code/item_079/parallel/my_module.py @@ -0,0 +1,23 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def gcd(pair): + a, b = pair + low = min(a, b) + for i in range(low, 0, -1): + if a % i == 0 and b % i == 0: + return i + raise RuntimeError("Not reachable") diff --git a/example_code/item_079/parallel/run_parallel.py b/example_code/item_079/parallel/run_parallel.py new file mode 100755 index 0000000..4103f2a --- /dev/null +++ b/example_code/item_079/parallel/run_parallel.py @@ -0,0 +1,43 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import my_module +from concurrent.futures import ProcessPoolExecutor +import time + +NUMBERS = [ + (19633090, 22659730), + (20306770, 38141720), + (15516450, 22296200), + (20390450, 20208020), + (18237120, 19249280), + (22931290, 10204910), + (12812380, 22737820), + (38238120, 42372810), + (38127410, 47291390), + (12923910, 21238110), +] + +def main(): + start = time.perf_counter() + pool = ProcessPoolExecutor(max_workers=8) # The one change + results = list(pool.map(my_module.gcd, NUMBERS)) + end = time.perf_counter() + delta = end - start + print(f"Took {delta:.3f} seconds") + +if __name__ == "__main__": + main() diff --git a/example_code/item_079/parallel/run_serial.py b/example_code/item_079/parallel/run_serial.py new file mode 100755 index 0000000..ba218bf --- /dev/null +++ b/example_code/item_079/parallel/run_serial.py @@ -0,0 +1,41 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import my_module +import time + +NUMBERS = [ + (19633090, 22659730), + (20306770, 38141720), + (15516450, 22296200), + (20390450, 20208020), + (18237120, 19249280), + (22931290, 10204910), + (12812380, 22737820), + (38238120, 42372810), + (38127410, 47291390), + (12923910, 21238110), +] + +def main(): + start = time.perf_counter() + results = list(map(my_module.gcd, NUMBERS)) + end = time.perf_counter() + delta = end - start + print(f"Took {delta:.3f} seconds") + +if __name__ == "__main__": + main() diff --git a/example_code/item_079/parallel/run_threads.py b/example_code/item_079/parallel/run_threads.py new file mode 100755 index 0000000..3eebc1a --- /dev/null +++ b/example_code/item_079/parallel/run_threads.py @@ -0,0 +1,43 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import my_module +from concurrent.futures import ThreadPoolExecutor +import time + +NUMBERS = [ + (19633090, 22659730), + (20306770, 38141720), + (15516450, 22296200), + (20390450, 20208020), + (18237120, 19249280), + (22931290, 10204910), + (12812380, 22737820), + (38238120, 42372810), + (38127410, 47291390), + (12923910, 21238110), +] + +def main(): + start = time.perf_counter() + pool = ThreadPoolExecutor(max_workers=8) + results = list(pool.map(my_module.gcd, NUMBERS)) + end = time.perf_counter() + delta = end - start + print(f"Took {delta:.3f} seconds") + +if __name__ == "__main__": + main() diff --git a/example_code/item_080.py b/example_code/item_080.py new file mode 100755 index 0000000..743f652 --- /dev/null +++ b/example_code/item_080.py @@ -0,0 +1,198 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def try_finally_example(filename): + print("* Opening file") + handle = open(filename, encoding="utf-8") # May raise OSError + try: + print("* Reading data") + return handle.read() # May raise UnicodeDecodeError + finally: + print("* Calling close()") + handle.close() # Always runs after try block + + +print("Example 2") +try: + filename = "random_data.txt" + + with open(filename, "wb") as f: + f.write(b"\xf1\xf2\xf3\xf4\xf5") # Invalid utf-8 + + data = try_finally_example(filename) + # This should not be reached. + import sys + + sys.exit(1) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +try: + try_finally_example("does_not_exist.txt") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 4") +import json + +def load_json_key(data, key): + try: + print("* Loading JSON data") + result_dict = json.loads(data) # May raise ValueError + except ValueError: + print("* Handling ValueError") + raise KeyError(key) + else: + print("* Looking up key") + return result_dict[key] # May raise KeyError + + +print("Example 5") +assert load_json_key('{"foo": "bar"}', "foo") == "bar" + + +print("Example 6") +try: + load_json_key('{"foo": bad payload', "foo") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 7") +try: + load_json_key('{"foo": "bar"}', "does not exist") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +UNDEFINED = object() +DIE_IN_ELSE_BLOCK = False + +def divide_json(path): + print("* Opening file") + handle = open(path, "r+") # May raise OSError + try: + print("* Reading data") + data = handle.read() # May raise UnicodeDecodeError + print("* Loading JSON data") + op = json.loads(data) # May raise ValueError + print("* Performing calculation") + value = op["numerator"] / op["denominator"] # May raise ZeroDivisionError + except ZeroDivisionError: + print("* Handling ZeroDivisionError") + return UNDEFINED + else: + print("* Writing calculation") + op["result"] = value + result = json.dumps(op) + handle.seek(0) # May raise OSError + if DIE_IN_ELSE_BLOCK: + import errno + import os + + raise OSError(errno.ENOSPC, os.strerror(errno.ENOSPC)) + handle.write(result) # May raise OSError + return value + finally: + print("* Calling close()") + handle.close() # Always runs + + +print("Example 9") +temp_path = "random_data.json" + +with open(temp_path, "w") as f: + f.write('{"numerator": 1, "denominator": 10}') + +assert divide_json(temp_path) == 0.1 + + +print("Example 10") +with open(temp_path, "w") as f: + f.write('{"numerator": 1, "denominator": 0}') + +assert divide_json(temp_path) is UNDEFINED + + +print("Example 11") +try: + with open(temp_path, "w") as f: + f.write('{"numerator": 1 bad data') + + divide_json(temp_path) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 12") +try: + with open(temp_path, "w") as f: + f.write('{"numerator": 1, "denominator": 10}') + DIE_IN_ELSE_BLOCK = True + + divide_json(temp_path) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_081.py b/example_code/item_081.py new file mode 100755 index 0000000..51747ef --- /dev/null +++ b/example_code/item_081.py @@ -0,0 +1,138 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + list_a = [1, 2, 3] + assert list_a, "a empty" + list_b = [] + assert list_b, "b empty" # Raises +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +try: + class EmptyError(Exception): + pass + + list_c = [] + if not list_c: + raise EmptyError("c empty") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +try: + raise EmptyError("From raise statement") +except EmptyError as e: + print(f"Caught: {e}") + + +print("Example 4") +try: + assert False, "From assert statement" +except AssertionError as e: + print(f"Caught: {e}") + + +print("Example 5") +class RatingError(Exception): + pass + +class Rating: + def __init__(self, max_rating): + if not (max_rating > 0): + raise RatingError("Invalid max_rating") + self.max_rating = max_rating + self.ratings = [] + + def rate(self, rating): + if not (0 < rating <= self.max_rating): + raise RatingError("Invalid rating") + self.ratings.append(rating) + + +print("Example 6") +try: + movie = Rating(5) + movie.rate(5) + movie.rate(7) # Raises +except: + logging.exception('Expected') +else: + assert False + + +print("Example 7") +class RatingInternal: + def __init__(self, max_rating): + assert max_rating > 0, f"Invalid {max_rating=}" + self.max_rating = max_rating + self.ratings = [] + + def rate(self, rating): + assert 0 < rating <= self.max_rating, f"Invalid {rating=}" + self.ratings.append(rating) + + +print("Example 8") +try: + movie = RatingInternal(5) + movie.rate(5) + movie.rate(7) # Raises +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_082.py b/example_code/item_082.py new file mode 100755 index 0000000..d1c594a --- /dev/null +++ b/example_code/item_082.py @@ -0,0 +1,146 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +from threading import Lock + +lock = Lock() +with lock: + # Do something while maintaining an invariant + pass + + +print("Example 2") +lock.acquire() +try: + # Do something while maintaining an invariant + pass +finally: + lock.release() + + +print("Example 3") +import logging + +logging.getLogger().setLevel(logging.WARNING) + +def my_function(): + logging.debug("Some debug data") + logging.error("Error log here") + logging.debug("More debug data") + + +print("Example 4") +my_function() + + +print("Example 5") +from contextlib import contextmanager + +@contextmanager +def debug_logging(level): + logger = logging.getLogger() + old_level = logger.getEffectiveLevel() + logger.setLevel(level) + try: + yield + finally: + logger.setLevel(old_level) + + +print("Example 6") +with debug_logging(logging.DEBUG): + print("* Inside:") + my_function() + +print("* After:") +my_function() + + +print("Example 7") +with open("my_output.txt", "w") as handle: + handle.write("This is some data!") + + +print("Example 8") +handle = open("my_output.txt", "w") +try: + handle.write("This is some data!") +finally: + handle.close() + + +print("Example 9") +@contextmanager +def log_level(level, name): + logger = logging.getLogger(name) + old_level = logger.getEffectiveLevel() + logger.setLevel(level) + try: + yield logger + finally: + logger.setLevel(old_level) + + +print("Example 10") +with log_level(logging.DEBUG, "my-log") as my_logger: + my_logger.debug(f"This is a message for {my_logger.name}!") + logging.debug("This will not print") + + +print("Example 11") +logger = logging.getLogger("my-log") +logger.debug("Debug will not print") +logger.error("Error will print") + + +print("Example 12") +with log_level(logging.DEBUG, "other-log") as my_logger: # Changed + my_logger.debug(f"This is a message for {my_logger.name}!") + logging.debug("This will not print") diff --git a/example_code/item_083.py b/example_code/item_083.py new file mode 100755 index 0000000..b3637db --- /dev/null +++ b/example_code/item_083.py @@ -0,0 +1,108 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +connection = ... + +class RpcError(Exception): + pass + +def lookup_request(connection): + raise RpcError("From lookup_request") + +def close_connection(connection): + print("Connection closed") + +try: + request = lookup_request(connection) +except RpcError: + print("Encountered error!") + close_connection(connection) + + +print("Example 2") +def lookup_request(connection): + # No error raised + return object() + +def is_cached(connection, request): + raise RpcError("From is_cached") + +try: + request = lookup_request(connection) + if is_cached(connection, request): + request = None +except RpcError: + print("Encountered error!") + close_connection(connection) + + +print("Example 3") +def is_closed(_): + pass + +if is_closed(connection): + # Was the connection closed because of an error + # in lookup_request or is_cached? + pass + + +print("Example 4") +try: + try: + request = lookup_request(connection) + except RpcError: + close_connection(connection) + else: + if is_cached(connection, request): # Moved + request = None +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_084.py b/example_code/item_084.py new file mode 100755 index 0000000..9a9f12e --- /dev/null +++ b/example_code/item_084.py @@ -0,0 +1,112 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + class MyError(Exception): + pass + + try: + raise MyError(123) + except MyError as e: + print(f"Inside {e=}") + + print(f"Outside {e=}") # Raises +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +try: + try: + raise MyError(123) + except MyError as e: + print(f"Inside {e=}") + finally: + print(f"Finally {e=}") # Raises +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +class OtherError(Exception): + pass + +result = "Unexpected exception" +try: + raise MyError(123) +except MyError as e: + result = e +except OtherError as e: + result = e +else: + result = "Success" +finally: + print(f"Log {result=}") + + +print("Example 4") +try: + del result + try: + raise OtherError(123) # Not handled + except MyError as e: + result = e + else: + result = "Success" + finally: + print(f"{result=}") # Raises +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_085.py b/example_code/item_085.py new file mode 100755 index 0000000..55f713b --- /dev/null +++ b/example_code/item_085.py @@ -0,0 +1,109 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def load_data(path): + open(path).read() + +def analyze_data(data): + return "my summary" + +def run_report(path): + data = load_data(path) + summary = analyze(data) + return summary + + +print("Example 2") +try: + summary = run_report("pizza_data-2024-01-28.csv") + print(summary) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +try: + summary = run_report("pizza_data.csv") +except FileNotFoundError: + print("Transient file error") +else: + print(summary) + + +print("Example 4") +try: + summary = run_report("pizza_data.csv") +except Exception: # Changed + print("Transient report issue") +else: + print(summary) + + +print("Example 5") +try: + def load_data(path): + pass + + run_report("my_data.csv") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 6") +try: + summary = run_report("my_data.csv") +except Exception as e: + print("Fail:", type(e), e) +else: + print(summary) diff --git a/example_code/item_086.py b/example_code/item_086.py new file mode 100755 index 0000000..1c52390 --- /dev/null +++ b/example_code/item_086.py @@ -0,0 +1,249 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + def do_processing(): + raise KeyboardInterrupt + + def main(argv): + while True: + try: + do_processing() # Interrupted + except Exception as e: + print("Error:", type(e), e) + + return 0 + + if __name__ == "__main__": + sys.exit(main(sys.argv)) + else: + main(["foo.csv"]) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +try: + with open("my_data.csv", "w") as f: + f.write("file exists") + + def do_processing(handle): + raise KeyboardInterrupt + + def main(argv): + data_path = argv[1] + handle = open(data_path, "w+") + + while True: + try: + do_processing(handle) + except Exception as e: + print("Error:", type(e), e) + except BaseException: + print("Cleaning up interrupt") + handle.flush() + handle.close() + return 1 + + return 0 + + if __name__ == "__main__": + sys.exit(main(sys.argv)) + else: + main(["ignore", "foo.csv"]) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +try: + def do_processing(handle): + raise KeyboardInterrupt + + def main(argv): + data_path = argv[1] + handle = open(data_path, "w+") + + try: + while True: + try: + do_processing(handle) + except Exception as e: + print("Error:", type(e), e) + finally: + print("Cleaning up finally") # Always runs + handle.flush() + handle.close() + + if __name__ == "__main__": + sys.exit(main(sys.argv)) + else: + main(["ignore", "foo.csv"]) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 4") +try: + def do_processing(): + raise KeyboardInterrupt + + def input(prompt): + print(f"{prompt}y") + return "y" + + def main(argv): + while True: + try: + do_processing() + except Exception as e: + print("Error:", type(e), e) + except KeyboardInterrupt: + found = input("Terminate? [y/n]: ") + if found == "y": + raise # Propagate the error + + if __name__ == "__main__": + sys.exit(main(sys.argv)) + else: + main(["ignore", "foo.csv"]) + + del input +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +import functools + +def log(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + try: + result = func(*args, **kwargs) + except Exception as e: + result = e + raise + finally: + print( + f"Called {func.__name__}" + f"(*{args!r}, **{kwargs!r}) " + f"got {result!r}" + ) + + return wrapper + + +print("Example 6") +try: + @log + def my_func(x): + x / 0 + + my_func(123) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 7") +try: + @log + def other_func(x): + if x > 0: + sys.exit(1) + + other_func(456) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +def fixed_log(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + try: + result = func(*args, **kwargs) + except BaseException as e: # Fixed + result = e + raise + finally: + print( + f"Called {func.__name__}" + f"(*{args!r}, **{kwargs!r}) " + f"got {result!r}" + ) + + return wrapper + + +print("Example 9") +try: + @fixed_log + def other_func(x): + if x > 0: + sys.exit(1) + + other_func(456) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_087.py b/example_code/item_087.py new file mode 100755 index 0000000..39ee62e --- /dev/null +++ b/example_code/item_087.py @@ -0,0 +1,129 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class Request: + def __init__(self, body): + self.body = body + self.response = None + +def do_work(data): + assert False, data + +def handle(request): + try: + do_work(request.body) + except BaseException as e: + print(repr(e)) + request.response = 400 # Bad request error + + +print("Example 2") +request = Request("My message") +handle(request) + + +print("Example 3") +import traceback + +def handle2(request): + try: + do_work(request.body) + except BaseException as e: + traceback.print_tb(e.__traceback__) # Changed + print(repr(e)) + request.response = 400 + +request = Request("My message 2") +handle2(request) + + +print("Example 4") +def handle3(request): + try: + do_work(request.body) + except BaseException as e: + stack = traceback.extract_tb(e.__traceback__) + for frame in stack: + print(frame.name) + print(repr(e)) + request.response = 400 + +request = Request("My message 3") +handle3(request) + + +print("Example 5") +import json + +def log_if_error(file_path, target, *args, **kwargs): + try: + target(*args, **kwargs) + except BaseException as e: + stack = traceback.extract_tb(e.__traceback__) + stack_without_wrapper = stack[1:] + trace_dict = dict( + stack=[item.name for item in stack_without_wrapper], + error_type=type(e).__name__, + error_message=str(e), + ) + json_data = json.dumps(trace_dict) + + with open(file_path, "a") as f: + f.write(json_data) + f.write("\n") + + +print("Example 6") +log_if_error("my_log.jsonl", do_work, "First error") +log_if_error("my_log.jsonl", do_work, "Second error") + +with open("my_log.jsonl") as f: + for line in f: + print(line, end="") diff --git a/example_code/item_088.py b/example_code/item_088.py new file mode 100755 index 0000000..d30e278 --- /dev/null +++ b/example_code/item_088.py @@ -0,0 +1,256 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + my_dict = {} + my_dict["does_not_exist"] +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +my_dict = {} +try: + my_dict["does_not_exist"] +except KeyError: + print("Could not find key!") + + +print("Example 3") +try: + class MissingError(Exception): + pass + + try: + my_dict["does_not_exist"] # Raises first exception + except KeyError: + raise MissingError("Oops!") # Raises second exception +except: + logging.exception('Expected') +else: + assert False + + +print("Example 4") +try: + try: + my_dict["does_not_exist"] + except KeyError: + raise MissingError("Oops!") +except MissingError as e: + print("Second:", repr(e)) + print("First: ", repr(e.__context__)) + + +print("Example 5") +def lookup(my_key): + try: + return my_dict[my_key] + except KeyError: + raise MissingError + + +print("Example 6") +my_dict["my key 1"] = 123 +print(lookup("my key 1")) + + +print("Example 7") +try: + print(lookup("my key 2")) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +def contact_server(my_key): + print(f"Looking up {my_key!r} in server") + return "my value 2" + +def lookup(my_key): + try: + return my_dict[my_key] + except KeyError: + result = contact_server(my_key) + my_dict[my_key] = result # Fill the local cache + return result + + +print("Example 9") +print("Call 1") +print("Result:", lookup("my key 2")) +print("Call 2") +print("Result:", lookup("my key 2")) + + +print("Example 10") +class ServerMissingKeyError(Exception): + pass + +def contact_server(my_key): + print(f"Looking up {my_key!r} in server") + raise ServerMissingKeyError + + +print("Example 11") +try: + print(lookup("my key 3")) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 12") +def lookup(my_key): + try: + return my_dict[my_key] + except KeyError: + try: + result = contact_server(my_key) + except ServerMissingKeyError: + raise MissingError # Convert the server error + else: + my_dict[my_key] = result # Fill the local cache + return result + + +print("Example 13") +try: + print(lookup("my key 4")) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 14") +def lookup_explicit(my_key): + try: + return my_dict[my_key] + except KeyError as e: # Changed + try: + result = contact_server(my_key) + except ServerMissingKeyError: + raise MissingError from e # Changed + else: + my_dict[my_key] = result + return result + + +print("Example 15") +try: + print(lookup_explicit("my key 5")) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 16") +try: + lookup_explicit("my key 6") +except Exception as e: + print("Exception:", repr(e)) + print("Context: ", repr(e.__context__)) + print("Cause: ", repr(e.__cause__)) + print("Suppress: ", repr(e.__suppress_context__)) + + +print("Example 17") +import traceback + +try: + lookup("my key 7") +except Exception as e: + stack = traceback.extract_tb(e.__traceback__) + for frame in stack: + print(frame.line) + + +print("Example 18") +def get_cause(exc): + if exc.__cause__ is not None: + return exc.__cause__ + elif not exc.__suppress_context__: + return exc.__context__ + else: + return None + + +print("Example 19") +try: + lookup("my key 8") +except Exception as e: + while e is not None: + stack = traceback.extract_tb(e.__traceback__) + for i, frame in enumerate(stack, 1): + print(i, frame.line) + e = get_cause(e) + if e: + print("Caused by") + + +print("Example 20") +def contact_server(key): + raise ServerMissingKeyError from None # Suppress + + +print("Example 21") +try: + print(lookup("my key 9")) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_089.py b/example_code/item_089.py new file mode 100755 index 0000000..188905a --- /dev/null +++ b/example_code/item_089.py @@ -0,0 +1,171 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def my_func(): + try: + return 123 + finally: + print("Finally my_func") + +print("Before") +print(my_func()) +print("After") + + +print("Example 2") +def my_generator(): + try: + yield 10 + yield 20 + yield 30 + finally: + print("Finally my_generator") + +print("Before") + +for i in my_generator(): + print(i) + +print("After") + + +print("Example 3") +it = my_generator() +print("Before") +print(next(it)) +print(next(it)) +print("After") + + +print("Example 4") +import gc + +del it +gc.collect() + + +print("Example 5") +def catching_generator(): + try: + yield 40 + yield 50 + yield 60 + except BaseException as e: # Catches GeneratorExit + print("Catching handler", type(e), e) + raise + + +print("Example 6") +it = catching_generator() +print("Before") +print(next(it)) +print(next(it)) +print("After") +del it +gc.collect() + + +print("Example 8") +with open("my_file.txt", "w") as f: + for _ in range(20): + f.write("a" * random.randint(0, 100)) + f.write("\n") + +def lengths_path(path): + try: + with open(path) as handle: + for i, line in enumerate(handle): + print(f"Line {i}") + yield len(line.strip()) + finally: + print("Finally lengths_path") + + +print("Example 9") +max_head = 0 +it = lengths_path("my_file.txt") + +for i, length in enumerate(it): + if i == 5: + break + else: + max_head = max(max_head, length) + +print(max_head) + + +print("Example 10") +del it +gc.collect() + + +print("Example 11") +def lengths_handle(handle): + try: + for i, line in enumerate(handle): + print(f"Line {i}") + yield len(line.strip()) + finally: + print("Finally lengths_handle") + + +print("Example 12") +max_head = 0 + +with open("my_file.txt") as handle: + it = lengths_handle(handle) + for i, length in enumerate(it): + if i == 5: + break + else: + max_head = max(max_head, length) + +print(max_head) +print("Handle closed:", handle.closed) diff --git a/example_code/item_089_example_07.py b/example_code/item_089_example_07.py new file mode 100755 index 0000000..88341fd --- /dev/null +++ b/example_code/item_089_example_07.py @@ -0,0 +1,38 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 7") +import gc +import sys + +def broken_generator(): + try: + yield 70 + yield 80 + except BaseException as e: + print("Broken handler", type(e), e) + raise RuntimeError("Broken") + +it = broken_generator() +print("Before") +print(next(it)) +print("After") +sys.stdout.flush() +del it +gc.collect() +print("Still going") diff --git a/example_code/item_090.py b/example_code/item_090.py new file mode 100755 index 0000000..83e8829 --- /dev/null +++ b/example_code/item_090.py @@ -0,0 +1,94 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + n = 3 + assert n % 2 == 0, f"{n=} not even" +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +try: + if __debug__: + if not (n % 2 == 0): + raise AssertionError(f"{n=} not even") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +try: + def expensive_check(x): + return x != 2 + + items = [1, 2, 3] + if __debug__: + for i in items: + assert expensive_check(i), f"Failed {i=}" +except: + logging.exception('Expected') +else: + assert False + + +print("Example 4") +try: + # This will not compile + source = """__debug__ = False""" + eval(source) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_091.py b/example_code/item_091.py new file mode 100755 index 0000000..00f02a0 --- /dev/null +++ b/example_code/item_091.py @@ -0,0 +1,86 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +x = eval("1 + 2") +print(x) + + +print("Example 2") +try: + eval( + """ + if True: + print('okay') + else: + print('no') + """ + ) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +global_scope = {"my_condition": False} +local_scope = {} + +exec( + """ +if my_condition: + x = 'yes' +else: + x = 'no' +""", + global_scope, + local_scope, +) + +print(local_scope) diff --git a/example_code/item_092.py b/example_code/item_092.py new file mode 100755 index 0000000..92f7c35 --- /dev/null +++ b/example_code/item_092.py @@ -0,0 +1,149 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def insertion_sort(data): + result = [] + for value in data: + insert_value(result, value) + return result + + +print("Example 2") +def insert_value(array, value): + for i, existing in enumerate(array): + if existing > value: + array.insert(i, value) + return + array.append(value) + + +print("Example 3") +from random import randint + +max_size = 12**4 +data = [randint(0, max_size) for _ in range(max_size)] +test = lambda: insertion_sort(data) + + +print("Example 4") +from cProfile import Profile + +profiler = Profile() +profiler.runcall(test) + + +print("Example 5") +from pstats import Stats + +stats = Stats(profiler) +stats = Stats(profiler, stream=STDOUT) +stats.strip_dirs() +stats.sort_stats("cumulative") +stats.print_stats() + + +print("Example 6") +from bisect import bisect_left + +def insert_value(array, value): + i = bisect_left(array, value) + array.insert(i, value) + + +print("Example 7") +profiler = Profile() +profiler.runcall(test) +stats = Stats(profiler, stream=STDOUT) +stats.strip_dirs() +stats.sort_stats("cumulative") +stats.print_stats() + + +print("Example 8") +def my_utility(a, b): + c = 1 + for i in range(100): + c += a * b + +def first_func(): + for _ in range(1000): + my_utility(4, 5) + +def second_func(): + for _ in range(10): + my_utility(1, 3) + +def my_program(): + for _ in range(20): + first_func() + second_func() + + +print("Example 9") +profiler = Profile() +profiler.runcall(my_program) +stats = Stats(profiler, stream=STDOUT) +stats.strip_dirs() +stats.sort_stats("cumulative") +stats.print_stats() + + +print("Example 10") +stats = Stats(profiler, stream=STDOUT) +stats.strip_dirs() +stats.sort_stats("cumulative") +stats.print_callers() + + +print("Example 11") +stats = Stats(profiler, stream=STDOUT) +stats.strip_dirs() +stats.sort_stats("cumulative") +stats.print_callees() diff --git a/example_code/item_093.py b/example_code/item_093.py new file mode 100755 index 0000000..bed38b1 --- /dev/null +++ b/example_code/item_093.py @@ -0,0 +1,127 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import timeit + +delay = timeit.timeit(stmt="1+2") +print(delay) + + +print("Example 2") +delay = timeit.timeit(stmt="1+2", number=100) +print(delay) + + +print("Example 3") +count = 1_000_000 + +delay = timeit.timeit(stmt="1+2", number=count) + +print(f"{delay/count*1e9:.2f} nanoseconds") + + +print("Example 4") +import random + +count = 100_000 + +delay = timeit.timeit( + setup=""" +numbers = list(range(10_000)) +random.shuffle(numbers) +probe = 7_777 +""", + stmt=""" +probe in numbers +""", + globals=globals(), + number=count, +) + +print(f"{delay/count*1e9:.2f} nanoseconds") + + +print("Example 5") +delay = timeit.timeit( + setup=""" +numbers = set(range(10_000)) +probe = 7_777 +""", + stmt=""" +probe in numbers +""", + globals=globals(), + number=count, +) + +print(f"{delay/count*1e9:.2f} nanoseconds") + + +print("Example 6") +def loop_sum(items): + total = 0 + for i in items: + total += i + return total + +count = 1000 + +delay = timeit.timeit( + setup="numbers = list(range(10_000))", + stmt="loop_sum(numbers)", + globals=globals(), + number=count, +) + +print(f"{delay/count*1e9:.2f} nanoseconds") + + +print("Example 7") +print(f"{delay/count/10_000*1e9:.2f} nanoseconds") diff --git a/example_code/item_094.py b/example_code/item_094.py new file mode 100755 index 0000000..53f8b6f --- /dev/null +++ b/example_code/item_094.py @@ -0,0 +1,57 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def dot_product(a, b): + result = 0 + for i, j in zip(a, b): + result += i * j + return result + +print(dot_product([1, 2], [3, 4])) diff --git a/example_code/item_095.py b/example_code/item_095.py new file mode 100755 index 0000000..e9a0472 --- /dev/null +++ b/example_code/item_095.py @@ -0,0 +1,143 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def dot_product(a, b): + result = 0 + for i, j in zip(a, b): + result += i * j + return result + +print(dot_product([1, 2], [3, 4])) + + +print("Example 2") +import ctypes + +library_path = ... + +import pathlib + +run_py = pathlib.Path(__file__) +tools_dir = run_py.parent +root_dir = tools_dir.parent +# TODO: Fix this for example code +library_path = root_dir / "Ch11" / "my_library" / "my_library.lib" + +my_library = ctypes.cdll.LoadLibrary(library_path) + + +print("Example 3") +print(my_library.dot_product) + + +print("Example 4") +my_library.dot_product.restype = ctypes.c_double + +vector_ptr = ctypes.POINTER(ctypes.c_double) +my_library.dot_product.argtypes = ( + ctypes.c_int, + vector_ptr, + vector_ptr, +) + + +print("Example 5") +size = 3 +vector3 = ctypes.c_double * size +a = vector3(1.0, 2.5, 3.5) +b = vector3(-7, 4, -12.1) + + +print("Example 6") +result = my_library.dot_product( + 3, + ctypes.cast(a, vector_ptr), + ctypes.cast(b, vector_ptr), +) +print(result) + + +print("Example 7") +def dot_product(a, b): + size = len(a) + assert len(b) == size + a_vector = vector3(*a) + b_vector = vector3(*b) + result = my_library.dot_product(size, a_vector, b_vector) + return result + +result = dot_product([1.0, 2.5, 3.5], [-7, 4, -12.1]) +print(result) + + +print("Example 8") +from unittest import TestCase + +class MyLibraryTest(TestCase): + + def test_dot_product(self): + vector3 = ctypes.c_double * size + a = vector3(1.0, 2.5, 3.5) + b = vector3(-7, 4, -12.1) + vector_ptr = ctypes.POINTER(ctypes.c_double) + result = my_library.dot_product( + 3, + ctypes.cast(a, vector_ptr), + ctypes.cast(b, vector_ptr), + ) + self.assertAlmostEqual(-39.35, result) + +import unittest + +suite = unittest.defaultTestLoader.loadTestsFromTestCase( + MyLibraryTest +) +# suite.debug() +unittest.TextTestRunner(stream=STDOUT).run(suite) diff --git a/example_code/item_096/my_extension/my_extension_test.py b/example_code/item_096/my_extension/my_extension_test.py new file mode 100755 index 0000000..bdf637e --- /dev/null +++ b/example_code/item_096/my_extension/my_extension_test.py @@ -0,0 +1,83 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# my_extension_test.py +import unittest +import my_extension + +class MyExtensionTest(unittest.TestCase): + + def test_empty(self): + result = my_extension.dot_product([], []) + self.assertAlmostEqual(0, result) + + def test_positive_result(self): + result = my_extension.dot_product( + [3, 4, 5], + [-1, 9, -2.5], + ) + self.assertAlmostEqual(20.5, result) + + def test_zero_result(self): + result = my_extension.dot_product( + [0, 0, 0], + [1, 1, 1], + ) + self.assertAlmostEqual(0, result) + + def test_negative_result(self): + result = my_extension.dot_product( + [-1, -1, -1], + [1, 1, 1], + ) + self.assertAlmostEqual(-3, result) + + def test_not_lists(self): + with self.assertRaises(TypeError) as context: + my_extension.dot_product((1, 2), [3, 4]) + self.assertEqual( + "Both arguments must be lists", str(context.exception) + ) + + with self.assertRaises(TypeError) as context: + my_extension.dot_product([1, 2], (3, 4)) + self.assertEqual( + "Both arguments must be lists", str(context.exception) + ) + + def test_mismatched_size(self): + with self.assertRaises(ValueError) as context: + my_extension.dot_product([1], [2, 3]) + self.assertEqual( + "Lists must be the same length", str(context.exception) + ) + + with self.assertRaises(ValueError) as context: + my_extension.dot_product([1, 2], [3]) + self.assertEqual( + "Lists must be the same length", str(context.exception) + ) + + def test_not_floatable(self): + with self.assertRaises(TypeError) as context: + my_extension.dot_product(["bad"], [1]) + self.assertEqual( + "must be real number, not str", str(context.exception) + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/example_code/item_096/my_extension/setup.py b/example_code/item_096/my_extension/setup.py new file mode 100755 index 0000000..ef33308 --- /dev/null +++ b/example_code/item_096/my_extension/setup.py @@ -0,0 +1,28 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# setup.py +from setuptools import Extension, setup + +setup( + name="my_extension", + ext_modules=[ + Extension( + name="my_extension", + sources=["init.c", "dot_product.c"], + ), + ], +) diff --git a/example_code/item_096/my_extension2/my_extension2_test.py b/example_code/item_096/my_extension2/my_extension2_test.py new file mode 100755 index 0000000..6b2cc12 --- /dev/null +++ b/example_code/item_096/my_extension2/my_extension2_test.py @@ -0,0 +1,96 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# my_extension2_test.py +import unittest +import my_extension2 + +class MyExtension2Test(unittest.TestCase): + + def test_decimals(self): + import decimal + + a = [decimal.Decimal(1), decimal.Decimal(2)] + b = [decimal.Decimal(3), decimal.Decimal(4)] + result = my_extension2.dot_product(a, b) + self.assertEqual(11, result) + + def test_not_lists(self): + result1 = my_extension2.dot_product( + (1, 2), + [3, 4], + ) + result2 = my_extension2.dot_product( + [1, 2], + (3, 4), + ) + result3 = my_extension2.dot_product( + range(1, 3), + range(3, 5), + ) + self.assertAlmostEqual(11, result1) + self.assertAlmostEqual(11, result2) + self.assertAlmostEqual(11, result3) + + def test_empty(self): + result = my_extension2.dot_product([], []) + self.assertAlmostEqual(0, result) + + def test_positive_result(self): + result = my_extension2.dot_product( + [3, 4, 5], + [-1, 9, -2.5], + ) + self.assertAlmostEqual(20.5, result) + + def test_zero_result(self): + result = my_extension2.dot_product( + [0, 0, 0], + [1, 1, 1], + ) + self.assertAlmostEqual(0, result) + + def test_negative_result(self): + result = my_extension2.dot_product( + [-1, -1, -1], + [1, 1, 1], + ) + self.assertAlmostEqual(-3, result) + + def test_mismatched_size(self): + with self.assertRaises(ValueError) as context: + my_extension2.dot_product([1], [2, 3]) + self.assertEqual( + "Arguments had unequal length", str(context.exception) + ) + + with self.assertRaises(ValueError) as context: + my_extension2.dot_product([1, 2], [3]) + self.assertEqual( + "Arguments had unequal length", str(context.exception) + ) + + def test_not_floatable(self): + with self.assertRaises(TypeError) as context: + my_extension2.dot_product(["bad"], [1]) + self.assertEqual( + "unsupported operand type(s) for +: 'int' and 'str'", + str(context.exception), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/example_code/item_098/mycli/adjust.py b/example_code/item_098/mycli/adjust.py new file mode 100755 index 0000000..cceaf3a --- /dev/null +++ b/example_code/item_098/mycli/adjust.py @@ -0,0 +1,21 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# adjust.py +# Fast initialization + +def do_adjust(path, brightness, contrast): + print(f"Adjusting! {brightness=} {contrast=}") diff --git a/example_code/item_098/mycli/enhance.py b/example_code/item_098/mycli/enhance.py new file mode 100755 index 0000000..3163acc --- /dev/null +++ b/example_code/item_098/mycli/enhance.py @@ -0,0 +1,24 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# enhance.py +# Very slow initialization +import time + +time.sleep(1) + +def do_enhance(path, amount): + print(f"Enhancing! {amount=}") diff --git a/example_code/item_098/mycli/global_lock_perf.py b/example_code/item_098/mycli/global_lock_perf.py new file mode 100755 index 0000000..76c51ac --- /dev/null +++ b/example_code/item_098/mycli/global_lock_perf.py @@ -0,0 +1,41 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# global_lock_perf.py +import timeit +import threading + +trials = 100_000_000 + +initialized = False +initialized_lock = threading.Lock() + +result = timeit.timeit( + stmt=""" +global initialized +# Speculatively check without the lock +if not initialized: + with initialized_lock: + # Double check after holding the lock + if not initialized: + # Do expensive initialization + initialized = True +""", + globals=globals(), + number=trials, +) + +print(f"{result/trials * 1e9:2.1f} nanos per call") diff --git a/example_code/item_098/mycli/import_perf.py b/example_code/item_098/mycli/import_perf.py new file mode 100755 index 0000000..cfaa5ce --- /dev/null +++ b/example_code/item_098/mycli/import_perf.py @@ -0,0 +1,29 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# import_perf.py +import timeit + +trials = 10_000_000 + +result = timeit.timeit( + setup="import enhance", + stmt="import enhance", + globals=globals(), + number=trials, +) + +print(f"{result/trials * 1e9:2.1f} nanos per call") diff --git a/example_code/item_098/mycli/mycli.py b/example_code/item_098/mycli/mycli.py new file mode 100755 index 0000000..8f28411 --- /dev/null +++ b/example_code/item_098/mycli/mycli.py @@ -0,0 +1,33 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# mycli.py +import adjust +import enhance +import parser + +def main(): + args = parser.PARSER.parse_args() + + if args.command == "enhance": + enhance.do_enhance(args.file, args.amount) + elif args.command == "adjust": + adjust.do_adjust(args.file, args.brightness, args.contrast) + else: + raise RuntimeError("Not reachable") + +if __name__ == "__main__": + main() diff --git a/example_code/item_098/mycli/mycli_faster.py b/example_code/item_098/mycli/mycli_faster.py new file mode 100755 index 0000000..edc5126 --- /dev/null +++ b/example_code/item_098/mycli/mycli_faster.py @@ -0,0 +1,35 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# mycli_faster.py +import parser + +def main(): + args = parser.PARSER.parse_args() + + if args.command == "enhance": + import enhance # Changed + + enhance.do_enhance(args.file, args.amount) + elif args.command == "adjust": + import adjust # Changed + + adjust.do_adjust(args.file, args.brightness, args.contrast) + else: + raise RuntimeError("Not reachable") + +if __name__ == "__main__": + main() diff --git a/example_code/item_098/mycli/parser.py b/example_code/item_098/mycli/parser.py new file mode 100755 index 0000000..6b4dbff --- /dev/null +++ b/example_code/item_098/mycli/parser.py @@ -0,0 +1,30 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# parser.py +import argparse + +PARSER = argparse.ArgumentParser() +PARSER.add_argument("file") + +sub_parsers = PARSER.add_subparsers(dest="command") + +enhance_parser = sub_parsers.add_parser("enhance") +enhance_parser.add_argument("--amount", type=float) + +adjust_parser = sub_parsers.add_parser("adjust") +adjust_parser.add_argument("--brightness", type=float) +adjust_parser.add_argument("--contrast", type=float) diff --git a/example_code/item_098/mycli/server.py b/example_code/item_098/mycli/server.py new file mode 100755 index 0000000..5629a96 --- /dev/null +++ b/example_code/item_098/mycli/server.py @@ -0,0 +1,43 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# server.py +from flask import Flask, render_template, request + +app = Flask(__name__) + +@app.route("/adjust", methods=["GET", "POST"]) +def do_adjust(): + if request.method == "POST": + the_file = request.files["the_file"] + brightness = request.form["brightness"] + contrast = request.form["contrast"] + import adjust # Dynamic import + + return adjust.do_adjust(the_file, brightness, contrast) + else: + return render_template("adjust.html") + +@app.route("/enhance", methods=["GET", "POST"]) +def do_enhance(): + if request.method == "POST": + the_file = request.files["the_file"] + amount = request.form["amount"] + import enhance # Dynamic import + + return enhance.do_enhance(the_file, amount) + else: + return render_template("enhance.html") diff --git a/example_code/item_099.py b/example_code/item_099.py new file mode 100755 index 0000000..0819f15 --- /dev/null +++ b/example_code/item_099.py @@ -0,0 +1,225 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def timecode_to_index(video_id, timecode): + return 1234 + # Returns the byte offset in the video data + +def request_chunk(video_id, byte_offset, size): + pass + # Returns size bytes of video_id's data from the offset + +video_id = ... +timecode = "01:09:14:28" +byte_offset = timecode_to_index(video_id, timecode) +size = 20 * 1024 * 1024 +video_data = request_chunk(video_id, byte_offset, size) + + +print("Example 2") +class NullSocket: + def __init__(self): + self.handle = open(os.devnull, "wb") + + def send(self, data): + self.handle.write(data) + +socket = ... # socket connection to client +video_data = ... # bytes containing data for video_id +byte_offset = ... # Requested starting position +size = 20 * 1024 * 1024 # Requested chunk size +import os + +socket = NullSocket() +video_data = 100 * os.urandom(1024 * 1024) +byte_offset = 1234 + +chunk = video_data[byte_offset : byte_offset + size] +socket.send(chunk) + + +print("Example 3") +import timeit + +def run_test(): + chunk = video_data[byte_offset : byte_offset + size] + # Call socket.send(chunk), but ignoring for benchmark + +result = ( + timeit.timeit( + stmt="run_test()", + globals=globals(), + number=100, + ) + / 100 +) + +print(f"{result:0.9f} seconds") + + +print("Example 4") +data = b"shave and a haircut, two bits" +view = memoryview(data) +chunk = view[12:19] +print(chunk) +print("Size: ", chunk.nbytes) +print("Data in view: ", chunk.tobytes()) +print("Underlying data:", chunk.obj) + + +print("Example 5") +video_view = memoryview(video_data) + +def run_test(): + chunk = video_view[byte_offset : byte_offset + size] + # Call socket.send(chunk), but ignoring for benchmark + +result = ( + timeit.timeit( + stmt="run_test()", + globals=globals(), + number=100, + ) + / 100 +) + +print(f"{result:0.9f} seconds") + + +print("Example 6") +class FakeSocket: + + def recv(self, size): + return video_view[byte_offset : byte_offset + size] + + def recv_into(self, buffer): + source_data = video_view[byte_offset : byte_offset + size] + buffer[:] = source_data + +socket = ... # socket connection to the client +video_cache = ... # Cache of incoming video stream +byte_offset = ... # Incoming buffer position +size = 1024 * 1024 # Incoming chunk size +socket = FakeSocket() +video_cache = video_data[:] +byte_offset = 1234 + +chunk = socket.recv(size) +video_view = memoryview(video_cache) +before = video_view[:byte_offset] +after = video_view[byte_offset + size :] +new_cache = b"".join([before, chunk, after]) + + +print("Example 7") +def run_test(): + chunk = socket.recv(size) + before = video_view[:byte_offset] + after = video_view[byte_offset + size :] + new_cache = b"".join([before, chunk, after]) + +result = ( + timeit.timeit( + stmt="run_test()", + globals=globals(), + number=100, + ) + / 100 +) + +print(f"{result:0.9f} seconds") + + +print("Example 8") +try: + my_bytes = b"hello" + my_bytes[0] = 0x79 +except: + logging.exception('Expected') +else: + assert False + + +print("Example 9") +my_array = bytearray(b"hello") +my_array[0] = 0x79 +print(my_array) + + +print("Example 10") +my_array = bytearray(b"row, row, row your boat") +my_view = memoryview(my_array) +write_view = my_view[3:13] +write_view[:] = b"-10 bytes-" +print(my_array) + + +print("Example 11") +video_array = bytearray(video_cache) +write_view = memoryview(video_array) +chunk = write_view[byte_offset : byte_offset + size] +socket.recv_into(chunk) + + +print("Example 12") +def run_test(): + chunk = write_view[byte_offset : byte_offset + size] + socket.recv_into(chunk) + +result = ( + timeit.timeit( + stmt="run_test()", + globals=globals(), + number=100, + ) + / 100 +) + +print(f"{result:0.9f} seconds") diff --git a/example_code/item_100.py b/example_code/item_100.py new file mode 100755 index 0000000..ed06714 --- /dev/null +++ b/example_code/item_100.py @@ -0,0 +1,172 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +numbers = [93, 86, 11, 68, 70] +numbers.sort() +print(numbers) + + +print("Example 2") +class Tool: + def __init__(self, name, weight): + self.name = name + self.weight = weight + + def __repr__(self): + return f"Tool({self.name!r}, {self.weight})" + +tools = [ + Tool("level", 3.5), + Tool("hammer", 1.25), + Tool("screwdriver", 0.5), + Tool("chisel", 0.25), +] + + +print("Example 3") +try: + tools.sort() +except: + logging.exception('Expected') +else: + assert False + + +print("Example 4") +print("Unsorted:", repr(tools)) +tools.sort(key=lambda x: x.name) +print("\nSorted: ", tools) + + +print("Example 5") +tools.sort(key=lambda x: x.weight) +print("By weight:", tools) + + +print("Example 6") +places = ["home", "work", "New York", "Paris"] +places.sort() +print("Case sensitive: ", places) +places.sort(key=lambda x: x.lower()) +print("Case insensitive:", places) + + +print("Example 7") +power_tools = [ + Tool("drill", 4), + Tool("circular saw", 5), + Tool("jackhammer", 40), + Tool("sander", 4), +] + + +print("Example 8") +saw = (5, "circular saw") +jackhammer = (40, "jackhammer") +assert not (jackhammer < saw) # Matches expectations + + +print("Example 9") +drill = (4, "drill") +sander = (4, "sander") +assert drill[0] == sander[0] # Same weight +assert drill[1] < sander[1] # Alphabetically less +assert drill < sander # Thus, drill comes first + + +print("Example 10") +power_tools.sort(key=lambda x: (x.weight, x.name)) +print(power_tools) + + +print("Example 11") +power_tools.sort( + key=lambda x: (x.weight, x.name), + reverse=True, # Makes all criteria descending +) +print(power_tools) + + +print("Example 12") +power_tools.sort(key=lambda x: (-x.weight, x.name)) +print(power_tools) + + +print("Example 13") +try: + power_tools.sort(key=lambda x: (x.weight, -x.name), reverse=True) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 14") +power_tools.sort( + key=lambda x: x.name, # Name ascending +) +power_tools.sort( + key=lambda x: x.weight, # Weight descending + reverse=True, +) +print(power_tools) + + +print("Example 15") +power_tools.sort(key=lambda x: x.name) +print(power_tools) + + +print("Example 16") +power_tools.sort( + key=lambda x: x.weight, + reverse=True, +) +print(power_tools) diff --git a/example_code/item_101.py b/example_code/item_101.py new file mode 100755 index 0000000..42bd7fd --- /dev/null +++ b/example_code/item_101.py @@ -0,0 +1,76 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +butterflies = ["Swallowtail", "Monarch", "Red Admiral"] +print(f"Before {butterflies}") +butterflies.sort() +print(f"After {butterflies}") + + +print("Example 2") +original = ["Swallowtail", "Monarch", "Red Admiral"] +alphabetical = sorted(original) +print(f"Original {original}") +print(f"Sorted {alphabetical}") + + +print("Example 3") +patterns = {"solid", "spotted", "cells"} +print(sorted(patterns)) + + +print("Example 4") +legs = {"insects": 6, "spiders": 8, "lizards": 4} +sorted_legs = sorted( + legs, + key=lambda x: legs[x], + reverse=True, +) +print(sorted_legs) diff --git a/example_code/item_102.py b/example_code/item_102.py new file mode 100755 index 0000000..b546c52 --- /dev/null +++ b/example_code/item_102.py @@ -0,0 +1,123 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +data = list(range(10**5)) +index = data.index(91234) +assert index == 91234 + + +print("Example 2") +def find_closest(sequence, goal): + for index, value in enumerate(sequence): + if goal < value: + return index + raise ValueError(f"{goal} is out of bounds") + +index = find_closest(data, 91234.56) +assert index == 91235 + +try: + find_closest(data, 100000000) +except ValueError: + pass # Expected +else: + assert False + + +print("Example 3") +from bisect import bisect_left + +index = bisect_left(data, 91234) # Exact match +assert index == 91234 + +index = bisect_left(data, 91234.56) # Closest match +assert index == 91235 + + +print("Example 4") +import random +import timeit + +size = 10**5 +iterations = 1000 + +data = list(range(size)) +to_lookup = [random.randint(0, size) for _ in range(iterations)] + +def run_linear(data, to_lookup): + for index in to_lookup: + data.index(index) + +def run_bisect(data, to_lookup): + for index in to_lookup: + bisect_left(data, index) + +baseline = ( + timeit.timeit( + stmt="run_linear(data, to_lookup)", + globals=globals(), + number=10, + ) + / 10 +) +print(f"Linear search takes {baseline:.6f}s") + +comparison = ( + timeit.timeit( + stmt="run_bisect(data, to_lookup)", + globals=globals(), + number=10, + ) + / 10 +) +print(f"Bisect search takes {comparison:.6f}s") + +slowdown = 1 + ((baseline - comparison) / comparison) +print(f"{slowdown:.1f}x slower") diff --git a/example_code/item_103.py b/example_code/item_103.py new file mode 100755 index 0000000..e4a691a --- /dev/null +++ b/example_code/item_103.py @@ -0,0 +1,243 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class Email: + def __init__(self, sender, receiver, message): + self.sender = sender + self.receiver = receiver + self.message = message + + +print("Example 2") +def get_emails(): + yield Email("foo@example.com", "bar@example.com", "hello1") + yield Email("baz@example.com", "banana@example.com", "hello2") + yield None + yield Email("meep@example.com", "butter@example.com", "hello3") + yield Email("stuff@example.com", "avocado@example.com", "hello4") + yield None + yield Email("thingy@example.com", "orange@example.com", "hello5") + yield Email("roger@example.com", "bob@example.com", "hello6") + yield None + yield Email("peanut@example.com", "alice@example.com", "hello7") + yield None + +EMAIL_IT = get_emails() + +class NoEmailError(Exception): + pass + +def try_receive_email(): + # Returns an Email instance or raises NoEmailError + try: + email = next(EMAIL_IT) + except StopIteration: + email = None + + if not email: + raise NoEmailError + + print(f"Produced email: {email.message}") + return email + + +print("Example 3") +def produce_emails(queue): + while True: + try: + email = try_receive_email() + except NoEmailError: + return + else: + queue.append(email) # Producer + + +print("Example 4") +def consume_one_email(queue): + if not queue: + return + email = queue.pop(0) # Consumer + # Index the message for long-term archival + print(f"Consumed email: {email.message}") + + +print("Example 5") +def loop(queue, keep_running): + while keep_running(): + produce_emails(queue) + consume_one_email(queue) + + +def make_test_end(): + count = list(range(10)) + + def func(): + if count: + count.pop() + return True + return False + + return func + + +def my_end_func(): + pass + +my_end_func = make_test_end() +loop([], my_end_func) + + +print("Example 6") +import timeit + +def list_append_benchmark(count): + def run(queue): + for i in range(count): + queue.append(i) + + return timeit.timeit( + setup="queue = []", + stmt="run(queue)", + globals=locals(), + number=1, + ) + + +print("Example 7") +for i in range(1, 6): + count = i * 1_000_000 + delay = list_append_benchmark(count) + print(f"Count {count:>5,} takes: {delay*1e3:>6.2f}ms") + + +print("Example 8") +def list_pop_benchmark(count): + def prepare(): + return list(range(count)) + + def run(queue): + while queue: + queue.pop(0) + + return timeit.timeit( + setup="queue = prepare()", + stmt="run(queue)", + globals=locals(), + number=1, + ) + + +print("Example 9") +for i in range(1, 6): + count = i * 10_000 + delay = list_pop_benchmark(count) + print(f"Count {count:>5,} takes: {delay*1e3:>6.2f}ms") + + +print("Example 10") +import collections + +def consume_one_email(queue): + if not queue: + return + email = queue.popleft() # Consumer + # Process the email message + print(f"Consumed email: {email.message}") + +def my_end_func(): + pass + +my_end_func = make_test_end() +EMAIL_IT = get_emails() +loop(collections.deque(), my_end_func) + + +print("Example 11") +def deque_append_benchmark(count): + def prepare(): + return collections.deque() + + def run(queue): + for i in range(count): + queue.append(i) + + return timeit.timeit( + setup="queue = prepare()", + stmt="run(queue)", + globals=locals(), + number=1, + ) + +for i in range(1, 6): + count = i * 100_000 + delay = deque_append_benchmark(count) + print(f"Count {count:>5,} takes: {delay*1e3:>6.2f}ms") + + +print("Example 12") +def dequeue_popleft_benchmark(count): + def prepare(): + return collections.deque(range(count)) + + def run(queue): + while queue: + queue.popleft() + + return timeit.timeit( + setup="queue = prepare()", + stmt="run(queue)", + globals=locals(), + number=1, + ) + +for i in range(1, 6): + count = i * 100_000 + delay = dequeue_popleft_benchmark(count) + print(f"Count {count:>5,} takes: {delay*1e3:>6.2f}ms") diff --git a/example_code/item_104.py b/example_code/item_104.py new file mode 100755 index 0000000..9073442 --- /dev/null +++ b/example_code/item_104.py @@ -0,0 +1,366 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class Book: + def __init__(self, title, due_date): + self.title = title + self.due_date = due_date + + +print("Example 2") +def add_book(queue, book): + queue.append(book) + queue.sort(key=lambda x: x.due_date, reverse=True) + +queue = [] +add_book(queue, Book("Don Quixote", "2019-06-07")) +add_book(queue, Book("Frankenstein", "2019-06-05")) +add_book(queue, Book("Les Misérables", "2019-06-08")) +add_book(queue, Book("War and Peace", "2019-06-03")) + + +print("Example 3") +class NoOverdueBooks(Exception): + pass + +def next_overdue_book(queue, now): + if queue: + book = queue[-1] + if book.due_date < now: + queue.pop() + return book + + raise NoOverdueBooks + + +print("Example 4") +now = "2019-06-10" + +found = next_overdue_book(queue, now) +print(found.due_date, found.title) + +found = next_overdue_book(queue, now) +print(found.due_date, found.title) + + +print("Example 5") +def return_book(queue, book): + queue.remove(book) + +queue = [] +book = Book("Treasure Island", "2019-06-04") + +add_book(queue, book) +print("Before return:", [x.title for x in queue]) + +return_book(queue, book) +print("After return: ", [x.title for x in queue]) + + +print("Example 6") +try: + next_overdue_book(queue, now) +except NoOverdueBooks: + pass # Expected +else: + assert False # Doesn't happen + + +print("Example 7") +import random +import timeit + +def list_overdue_benchmark(count): + def prepare(): + to_add = list(range(count)) + random.shuffle(to_add) + return [], to_add + + def run(queue, to_add): + for i in to_add: + queue.append(i) + queue.sort(reverse=True) + + while queue: + queue.pop() + + return timeit.timeit( + setup="queue, to_add = prepare()", + stmt=f"run(queue, to_add)", + globals=locals(), + number=1, + ) + + +print("Example 8") +for i in range(1, 6): + count = i * 1_000 + delay = list_overdue_benchmark(count) + print(f"Count {count:>5,} takes: {delay*1e3:>6.2f}ms") + + +print("Example 9") +def list_return_benchmark(count): + def prepare(): + queue = list(range(count)) + random.shuffle(queue) + + to_return = list(range(count)) + random.shuffle(to_return) + + return queue, to_return + + def run(queue, to_return): + for i in to_return: + queue.remove(i) + + return timeit.timeit( + setup="queue, to_return = prepare()", + stmt=f"run(queue, to_return)", + globals=locals(), + number=1, + ) + + +print("Example 10") +for i in range(1, 6): + count = i * 1_000 + delay = list_return_benchmark(count) + print(f"Count {count:>5,} takes: {delay*1e3:>6.2f}ms") + + +print("Example 11") +from heapq import heappush + +def add_book(queue, book): + heappush(queue, book) + + +print("Example 12") +try: + queue = [] + add_book(queue, Book("Little Women", "2019-06-05")) + add_book(queue, Book("The Time Machine", "2019-05-30")) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 13") +import functools + +@functools.total_ordering +class Book: + def __init__(self, title, due_date): + self.title = title + self.due_date = due_date + + def __lt__(self, other): + return self.due_date < other.due_date + + +print("Example 14") +queue = [] +add_book(queue, Book("Pride and Prejudice", "2019-06-01")) +add_book(queue, Book("The Time Machine", "2019-05-30")) +add_book(queue, Book("Crime and Punishment", "2019-06-06")) +add_book(queue, Book("Wuthering Heights", "2019-06-12")) +print([b.title for b in queue]) + + +print("Example 15") +queue = [ + Book("Pride and Prejudice", "2019-06-01"), + Book("The Time Machine", "2019-05-30"), + Book("Crime and Punishment", "2019-06-06"), + Book("Wuthering Heights", "2019-06-12"), +] +queue.sort() +print([b.title for b in queue]) + + +print("Example 16") +from heapq import heapify + +queue = [ + Book("Pride and Prejudice", "2019-06-01"), + Book("The Time Machine", "2019-05-30"), + Book("Crime and Punishment", "2019-06-06"), + Book("Wuthering Heights", "2019-06-12"), +] +heapify(queue) +print([b.title for b in queue]) + + +print("Example 17") +from heapq import heappop + +def next_overdue_book(queue, now): + if queue: + book = queue[0] # Most overdue first + if book.due_date < now: + heappop(queue) # Remove the overdue book + return book + + raise NoOverdueBooks + + +print("Example 18") +now = "2019-06-02" + +book = next_overdue_book(queue, now) +print(book.due_date, book.title) + +book = next_overdue_book(queue, now) +print(book.due_date, book.title) + +try: + next_overdue_book(queue, now) +except NoOverdueBooks: + pass # Expected +else: + assert False # Doesn't happen + + +print("Example 19") +def heap_overdue_benchmark(count): + def prepare(): + to_add = list(range(count)) + random.shuffle(to_add) + return [], to_add + + def run(queue, to_add): + for i in to_add: + heappush(queue, i) + while queue: + heappop(queue) + + return timeit.timeit( + setup="queue, to_add = prepare()", + stmt=f"run(queue, to_add)", + globals=locals(), + number=1, + ) + + +print("Example 20") +for i in range(1, 6): + count = i * 10_000 + delay = heap_overdue_benchmark(count) + print(f"Count {count:>5,} takes: {delay*1e3:>6.2f}ms") + + +print("Example 21") +@functools.total_ordering +class Book: + def __init__(self, title, due_date): + self.title = title + self.due_date = due_date + self.returned = False # New field + + def __lt__(self, other): + return self.due_date < other.due_date + + +print("Example 22") +def next_overdue_book(queue, now): + while queue: + book = queue[0] + if book.returned: + heappop(queue) + continue + + if book.due_date < now: + heappop(queue) + return book + + break + + raise NoOverdueBooks + + +queue = [] + +book = Book("Pride and Prejudice", "2019-06-01") +add_book(queue, book) + +book = Book("The Time Machine", "2019-05-30") +add_book(queue, book) +book.returned = True + +book = Book("Crime and Punishment", "2019-06-06") +add_book(queue, book) +book.returned = True + +book = Book("Wuthering Heights", "2019-06-12") +add_book(queue, book) + +now = "2019-06-11" + +book = next_overdue_book(queue, now) +assert book.title == "Pride and Prejudice" + +try: + next_overdue_book(queue, now) +except NoOverdueBooks: + pass # Expected +else: + assert False # Doesn't happen + + +print("Example 23") +def return_book(queue, book): + book.returned = True + + +assert not book.returned +return_book(queue, book) +assert book.returned diff --git a/example_code/item_105.py b/example_code/item_105.py new file mode 100755 index 0000000..50d3523 --- /dev/null +++ b/example_code/item_105.py @@ -0,0 +1,122 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import time + +now = 1710047865.0 +local_tuple = time.localtime(now) +time_format = "%Y-%m-%d %H:%M:%S" +time_str = time.strftime(time_format, local_tuple) +print(time_str) + + +print("Example 2") +time_tuple = time.strptime(time_str, time_format) +utc_now = time.mktime(time_tuple) +print(utc_now) + + +print("Example 3") +parse_format = "%Y-%m-%d %H:%M:%S %Z" +depart_sfo = "2024-03-09 21:17:45 PST" +time_tuple = time.strptime(depart_sfo, parse_format) +time_str = time.strftime(time_format, time_tuple) +print(time_str) + + +print("Example 4") +try: + arrival_nyc = "2024-03-10 03:31:18 EDT" + time_tuple = time.strptime(arrival_nyc, parse_format) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +from datetime import datetime, timezone + +now = datetime(2024, 3, 10, 5, 17, 45) +now_utc = now.replace(tzinfo=timezone.utc) +now_local = now_utc.astimezone() +print(now_local) + + +print("Example 6") +time_str = "2024-03-09 21:17:45" +now = datetime.strptime(time_str, time_format) +time_tuple = now.timetuple() +utc_now = time.mktime(time_tuple) +print(utc_now) + + +print("Example 7") +from zoneinfo import ZoneInfo + +arrival_nyc = "2024-03-10 03:31:18" +nyc_dt_naive = datetime.strptime(arrival_nyc, time_format) +eastern = ZoneInfo("US/Eastern") +nyc_dt = nyc_dt_naive.replace(tzinfo=eastern) +utc_dt = nyc_dt.astimezone(timezone.utc) +print("EDT:", nyc_dt) +print("UTC:", utc_dt) + + +print("Example 8") +pacific = ZoneInfo("US/Pacific") +sf_dt = utc_dt.astimezone(pacific) +print("PST:", sf_dt) + + +print("Example 9") +nepal = ZoneInfo("Asia/Katmandu") +nepal_dt = utc_dt.astimezone(nepal) +print("NPT", nepal_dt) diff --git a/example_code/item_106.py b/example_code/item_106.py new file mode 100755 index 0000000..5974021 --- /dev/null +++ b/example_code/item_106.py @@ -0,0 +1,100 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +rate = 1.45 +seconds = 3 * 60 + 42 +cost = rate * seconds / 60 +print(cost) + + +print("Example 2") +print(round(cost, 2)) + + +print("Example 3") +from decimal import Decimal + +rate = Decimal("1.45") +seconds = Decimal(3 * 60 + 42) +cost = rate * seconds / Decimal(60) +print(cost) + + +print("Example 4") +print(Decimal("1.45")) +print(Decimal(1.45)) + + +print("Example 5") +print("456") +print(456) + + +print("Example 6") +rate = Decimal("0.05") +seconds = Decimal("5") +small_cost = rate * seconds / Decimal(60) +print(small_cost) + + +print("Example 7") +print(round(small_cost, 2)) + + +print("Example 8") +from decimal import ROUND_UP + +rounded = cost.quantize(Decimal("0.01"), rounding=ROUND_UP) +print(f"Rounded {cost} to {rounded}") + + +print("Example 9") +rounded = small_cost.quantize(Decimal("0.01"), rounding=ROUND_UP) +print(f"Rounded {small_cost} to {rounded}") diff --git a/example_code/item_107.py b/example_code/item_107.py new file mode 100755 index 0000000..05c9099 --- /dev/null +++ b/example_code/item_107.py @@ -0,0 +1,227 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class GameState: + def __init__(self): + self.level = 0 + self.lives = 4 + + +print("Example 2") +state = GameState() +state.level += 1 # Player beat a level +state.lives -= 1 # Player had to try again + +print(state.__dict__) + + +print("Example 3") +import pickle + +state_path = "game_state.bin" +with open(state_path, "wb") as f: + pickle.dump(state, f) + + +print("Example 4") +with open(state_path, "rb") as f: + state_after = pickle.load(f) + +print(state_after.__dict__) + + +print("Example 5") +class GameState: + def __init__(self): + self.level = 0 + self.lives = 4 + self.points = 0 # New field + + +print("Example 6") +state = GameState() +serialized = pickle.dumps(state) +state_after = pickle.loads(serialized) + +print(state_after.__dict__) + + +print("Example 7") +with open(state_path, "rb") as f: + state_after = pickle.load(f) + +print(state_after.__dict__) + + +print("Example 8") +assert isinstance(state_after, GameState) + + +print("Example 9") +class GameState: + def __init__(self, level=0, lives=4, points=0): + self.level = level + self.lives = lives + self.points = points + + +print("Example 10") +def pickle_game_state(game_state): + kwargs = game_state.__dict__ + return unpickle_game_state, (kwargs,) + + +print("Example 11") +def unpickle_game_state(kwargs): + return GameState(**kwargs) + + +print("Example 12") +import copyreg + +copyreg.pickle(GameState, pickle_game_state) + + +print("Example 13") +state = GameState() +state.points += 1000 +serialized = pickle.dumps(state) +state_after = pickle.loads(serialized) +print(state_after.__dict__) + + +print("Example 14") +class GameState: + def __init__(self, level=0, lives=4, points=0, magic=5): + self.level = level + self.lives = lives + self.points = points + self.magic = magic # New field + + +print("Example 15") +print("Before:", state.__dict__) +state_after = pickle.loads(serialized) +print("After: ", state_after.__dict__) + + +print("Example 16") +class GameState: + def __init__(self, level=0, points=0, magic=5): + self.level = level + self.points = points + self.magic = magic + + +print("Example 17") +try: + pickle.loads(serialized) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 18") +def pickle_game_state(game_state): + kwargs = game_state.__dict__ + kwargs["version"] = 2 + return unpickle_game_state, (kwargs,) + + +print("Example 19") +def unpickle_game_state(kwargs): + version = kwargs.pop("version", 1) + if version == 1: + del kwargs["lives"] + return GameState(**kwargs) + + +print("Example 20") +copyreg.pickle(GameState, pickle_game_state) +print("Before:", state.__dict__) +state_after = pickle.loads(serialized) +print("After: ", state_after.__dict__) + + +print("Example 21") +copyreg.dispatch_table.clear() +state = GameState() +serialized = pickle.dumps(state) +del GameState + +class BetterGameState: + def __init__(self, level=0, points=0, magic=5): + self.level = level + self.points = points + self.magic = magic + + +print("Example 22") +try: + pickle.loads(serialized) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 23") +print(serialized) + + +print("Example 24") +copyreg.pickle(BetterGameState, pickle_game_state) + + +print("Example 25") +state = BetterGameState() +serialized = pickle.dumps(state) +print(serialized) diff --git a/example_code/item_108/testing/assert_test.py b/example_code/item_108/testing/assert_test.py new file mode 100755 index 0000000..8ff62cb --- /dev/null +++ b/example_code/item_108/testing/assert_test.py @@ -0,0 +1,32 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase, main +from utils import to_str + +class AssertTestCase(TestCase): + def test_assert_helper(self): + expected = 12 + found = 2 * 5 + self.assertEqual(expected, found) + + def test_assert_statement(self): + expected = 12 + found = 2 * 5 + assert expected == found + +if __name__ == "__main__": + main() diff --git a/example_code/item_108/testing/data_driven_test.py b/example_code/item_108/testing/data_driven_test.py new file mode 100755 index 0000000..b08c614 --- /dev/null +++ b/example_code/item_108/testing/data_driven_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase, main +from utils import to_str + +class DataDrivenTestCase(TestCase): + def test_good(self): + good_cases = [ + (b"my bytes", "my bytes"), + ("no error", b"no error"), # This one will fail + ("other str", "other str"), + ] + for value, expected in good_cases: + with self.subTest(value): + self.assertEqual(expected, to_str(value)) + + def test_bad(self): + bad_cases = [ + (object(), TypeError), + (b"\xfa\xfa", UnicodeDecodeError), + ] + for value, exception in bad_cases: + with self.subTest(value): + with self.assertRaises(exception): + to_str(value) + +if __name__ == "__main__": + main() diff --git a/example_code/item_108/testing/helper_test.py b/example_code/item_108/testing/helper_test.py new file mode 100755 index 0000000..c2d734a --- /dev/null +++ b/example_code/item_108/testing/helper_test.py @@ -0,0 +1,59 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase, main + +def sum_squares(values): + cumulative = 0 + for value in values: + cumulative += value**2 + yield cumulative + +class HelperTestCase(TestCase): + def verify_complex_case(self, values, expected): + expect_it = iter(expected) + found_it = iter(sum_squares(values)) + test_it = zip(expect_it, found_it, strict=True) + + for i, (expect, found) in enumerate(test_it): + if found != expect: + self.fail(f"Index {i} is wrong: {found} != {expect}") + + def test_too_short(self): + values = [1.1, 2.2] + expected = [1.1**2] + self.verify_complex_case(values, expected) + + def test_too_long(self): + values = [1.1, 2.2] + expected = [ + 1.1**2, + 1.1**2 + 2.2**2, + 0, # Value doesn't matter + ] + self.verify_complex_case(values, expected) + + def test_wrong_results(self): + values = [1.1, 2.2, 3.3] + expected = [ + 1.1**2, + 1.1**2 + 2.2**2, + 1.1**2 + 2.2**2 + 3.3**2 + 4.4**2, + ] + self.verify_complex_case(values, expected) + +if __name__ == "__main__": + main() diff --git a/example_code/item_108/testing/utils.py b/example_code/item_108/testing/utils.py new file mode 100755 index 0000000..7d14a78 --- /dev/null +++ b/example_code/item_108/testing/utils.py @@ -0,0 +1,23 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def to_str(data): + if isinstance(data, str): + return data + elif isinstance(data, bytes): + return data.decode("utf-8") + else: + raise TypeError(f"Must supply str or bytes, found: {data}") diff --git a/example_code/item_108/testing/utils_error_test.py b/example_code/item_108/testing/utils_error_test.py new file mode 100755 index 0000000..a47f9b8 --- /dev/null +++ b/example_code/item_108/testing/utils_error_test.py @@ -0,0 +1,30 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase, main +from utils import to_str + +class UtilsErrorTestCase(TestCase): + def test_to_str_bad(self): + with self.assertRaises(TypeError): + to_str(object()) + + def test_to_str_bad_encoding(self): + with self.assertRaises(UnicodeDecodeError): + to_str(b"\xfa\xfa") + +if __name__ == "__main__": + main() diff --git a/example_code/item_108/testing/utils_test.py b/example_code/item_108/testing/utils_test.py new file mode 100755 index 0000000..56fe1a8 --- /dev/null +++ b/example_code/item_108/testing/utils_test.py @@ -0,0 +1,31 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase, main +from utils import to_str + +class UtilsTestCase(TestCase): + def test_to_str_bytes(self): + self.assertEqual("hello", to_str(b"hello")) + + def test_to_str_str(self): + self.assertEqual("hello", to_str("hello")) + + def test_failing(self): + self.assertEqual("incorrect", to_str("hello")) + +if __name__ == "__main__": + main() diff --git a/example_code/item_109.py b/example_code/item_109.py new file mode 100755 index 0000000..35e7586 --- /dev/null +++ b/example_code/item_109.py @@ -0,0 +1,213 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class Toaster: + def __init__(self, timer): + self.timer = timer + self.doneness = 3 + self.hot = False + + def _get_duration(self): + return max(0.1, min(120, self.doneness * 10)) + + def push_down(self): + if self.hot: + return + + self.hot = True + self.timer.countdown(self._get_duration(), self.pop_up) + + def pop_up(self): + print("Pop!") # Release the spring + self.hot = False + self.timer.end() + + +print("Example 2") +import threading + +class ReusableTimer: + def __init__(self): + self.timer = None + + def countdown(self, duration, callback): + self.end() + self.timer = threading.Timer(duration, callback) + self.timer.start() + + def end(self): + if self.timer: + self.timer.cancel() + + +print("Example 3") +toaster = Toaster(ReusableTimer()) +print("Initially hot: ", toaster.hot) +toaster.doneness = 5 +toaster.doneness = 0 +toaster.push_down() +print("After push down:", toaster.hot) + +# Time passes +toaster.timer.timer.join() +print("After time: ", toaster.hot) + + +print("Example 4") +from unittest import TestCase +from unittest.mock import Mock + +class ToasterUnitTest(TestCase): + + def test_start(self): + timer = Mock(spec=ReusableTimer) + toaster = Toaster(timer) + toaster.push_down() + self.assertTrue(toaster.hot) + timer.countdown.assert_called_once_with(30, toaster.pop_up) + + def test_end(self): + timer = Mock(spec=ReusableTimer) + toaster = Toaster(timer) + toaster.hot = True + toaster.pop_up() + self.assertFalse(toaster.hot) + timer.end.assert_called_once() + +import unittest + +suite = unittest.defaultTestLoader.loadTestsFromTestCase( + ToasterUnitTest +) +# suite.debug() +unittest.TextTestRunner(stream=STDOUT).run(suite) + + +print("Example 5") +from unittest import mock + +class ReusableTimerUnitTest(TestCase): + + def test_countdown(self): + my_func = lambda: None + with mock.patch("threading.Timer"): + timer = ReusableTimer() + timer.countdown(0.1, my_func) + threading.Timer.assert_called_once_with(0.1, my_func) + timer.timer.start.assert_called_once() + + def test_end(self): + my_func = lambda: None + with mock.patch("threading.Timer"): + timer = ReusableTimer() + timer.countdown(0.1, my_func) + timer.end() + timer.timer.cancel.assert_called_once() + +import unittest + +suite = unittest.defaultTestLoader.loadTestsFromTestCase( + ReusableTimerUnitTest +) +# suite.debug() +unittest.TextTestRunner(stream=STDOUT).run(suite) + + +print("Example 6") +class ToasterIntegrationTest(TestCase): + + def setUp(self): + self.timer = ReusableTimer() + self.toaster = Toaster(self.timer) + self.toaster.doneness = 0 + + def test_wait_finish(self): + self.assertFalse(self.toaster.hot) + self.toaster.push_down() + self.assertTrue(self.toaster.hot) + self.timer.timer.join() + self.assertFalse(self.toaster.hot) + + def test_cancel_early(self): + self.assertFalse(self.toaster.hot) + self.toaster.push_down() + self.assertTrue(self.toaster.hot) + self.toaster.pop_up() + self.assertFalse(self.toaster.hot) + +import unittest + +suite = unittest.defaultTestLoader.loadTestsFromTestCase( + ToasterIntegrationTest +) +# suite.debug() +unittest.TextTestRunner(stream=STDOUT).run(suite) + + +print("Example 7") +class DonenessUnitTest(TestCase): + def setUp(self): + self.toaster = Toaster(ReusableTimer()) + + def test_min(self): + self.toaster.doneness = 0 + self.assertEqual(0.1, self.toaster._get_duration()) + + def test_max(self): + self.toaster.doneness = 1000 + self.assertEqual(120, self.toaster._get_duration()) + +import unittest + +suite = unittest.defaultTestLoader.loadTestsFromTestCase( + DonenessUnitTest +) +# suite.debug() +unittest.TextTestRunner(stream=STDOUT).run(suite) diff --git a/example_code/item_110/testing/environment_test.py b/example_code/item_110/testing/environment_test.py new file mode 100755 index 0000000..500c6f2 --- /dev/null +++ b/example_code/item_110/testing/environment_test.py @@ -0,0 +1,34 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest import TestCase, main + +class EnvironmentTest(TestCase): + def setUp(self): + self.test_dir = TemporaryDirectory() + self.test_path = Path(self.test_dir.name) + + def tearDown(self): + self.test_dir.cleanup() + + def test_modify_file(self): + with open(self.test_path / "data.bin", "w") as f: + f.write("hello") + +if __name__ == "__main__": + main() diff --git a/example_code/item_110/testing/integration_test.py b/example_code/item_110/testing/integration_test.py new file mode 100755 index 0000000..dbc614b --- /dev/null +++ b/example_code/item_110/testing/integration_test.py @@ -0,0 +1,39 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase, main + +def setUpModule(): + print("* Module setup") + +def tearDownModule(): + print("* Module clean-up") + +class IntegrationTest(TestCase): + def setUp(self): + print("* Test setup") + + def tearDown(self): + print("* Test clean-up") + + def test_end_to_end1(self): + print("* Test 1") + + def test_end_to_end2(self): + print("* Test 2") + +if __name__ == "__main__": + main() diff --git a/example_code/item_111.py b/example_code/item_111.py new file mode 100755 index 0000000..67099d7 --- /dev/null +++ b/example_code/item_111.py @@ -0,0 +1,323 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class DatabaseConnection: + def __init__(self, host, port): + pass + + +class DatabaseConnectionError(Exception): + pass + +def get_animals(database, species): + # Query the Database + raise DatabaseConnectionError("Not connected") + # Return a list of (name, last_mealtime) tuples + + +print("Example 2") +try: + database = DatabaseConnection("localhost", "4444") + + get_animals(database, "Meerkat") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +from datetime import datetime +from unittest.mock import Mock + +mock = Mock(spec=get_animals) +expected = [ + ("Spot", datetime(2024, 6, 5, 11, 15)), + ("Fluffy", datetime(2024, 6, 5, 12, 30)), + ("Jojo", datetime(2024, 6, 5, 12, 45)), +] +mock.return_value = expected + + +print("Example 4") +try: + mock.does_not_exist +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +database = object() +result = mock(database, "Meerkat") +assert result == expected + + +print("Example 6") +mock.assert_called_once_with(database, "Meerkat") + + +print("Example 7") +try: + mock.assert_called_once_with(database, "Giraffe") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +from unittest.mock import ANY + +mock = Mock(spec=get_animals) +mock("database 1", "Rabbit") +mock("database 2", "Bison") +mock("database 3", "Meerkat") + +mock.assert_called_with(ANY, "Meerkat") + + +print("Example 9") +try: + class MyError(Exception): + pass + + mock = Mock(spec=get_animals) + mock.side_effect = MyError("Whoops! Big problem") + result = mock(database, "Meerkat") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 10") +def get_food_period(database, species): + # Query the Database + pass + # Return a time delta + +def feed_animal(database, name, when): + # Write to the Database + pass + +def do_rounds(database, species): + now = datetime.now() + feeding_timedelta = get_food_period(database, species) + animals = get_animals(database, species) + fed = 0 + + for name, last_mealtime in animals: + if (now - last_mealtime) > feeding_timedelta: + feed_animal(database, name, now) + fed += 1 + + return fed + + +print("Example 11") +def do_rounds( + database, + species, + *, + now_func=datetime.now, + food_func=get_food_period, + animals_func=get_animals, + feed_func=feed_animal +): + now = now_func() + feeding_timedelta = food_func(database, species) + animals = animals_func(database, species) + fed = 0 + + for name, last_mealtime in animals: + if (now - last_mealtime) > feeding_timedelta: + feed_func(database, name, now) + fed += 1 + + return fed + + +print("Example 12") +from datetime import timedelta + +now_func = Mock(spec=datetime.now) +now_func.return_value = datetime(2024, 6, 5, 15, 45) + +food_func = Mock(spec=get_food_period) +food_func.return_value = timedelta(hours=3) + +animals_func = Mock(spec=get_animals) +animals_func.return_value = [ + ("Spot", datetime(2024, 6, 5, 11, 15)), + ("Fluffy", datetime(2024, 6, 5, 12, 30)), + ("Jojo", datetime(2024, 6, 5, 12, 45)), +] + +feed_func = Mock(spec=feed_animal) + + +print("Example 13") +result = do_rounds( + database, + "Meerkat", + now_func=now_func, + food_func=food_func, + animals_func=animals_func, + feed_func=feed_func, +) + +assert result == 2 + + +print("Example 14") +from unittest.mock import call + +food_func.assert_called_once_with(database, "Meerkat") + +animals_func.assert_called_once_with(database, "Meerkat") + +feed_func.assert_has_calls( + [ + call(database, "Spot", now_func.return_value), + call(database, "Fluffy", now_func.return_value), + ], + any_order=True, +) + +# Make sure these variables don't pollute later tests +del food_func +del animals_func +del feed_func + + +print("Example 15") +from unittest.mock import patch + +print("Outside patch:", get_animals) + +with patch("__main__.get_animals"): + print("Inside patch: ", get_animals) + +print("Outside again:", get_animals) + + +print("Example 16") +try: + fake_now = datetime(2024, 6, 5, 15, 45) + + with patch("datetime.datetime.now"): + datetime.now.return_value = fake_now +except: + logging.exception('Expected') +else: + assert False + + +print("Example 17") +def get_do_rounds_time(): + return datetime.now() + +def do_rounds(database, species): + now = get_do_rounds_time() + +with patch("__main__.get_do_rounds_time"): + pass + + +print("Example 18") +def do_rounds(database, species, *, now_func=datetime.now): + now = now_func() + feeding_timedelta = get_food_period(database, species) + animals = get_animals(database, species) + fed = 0 + + for name, last_mealtime in animals: + if (now - last_mealtime) > feeding_timedelta: + feed_animal(database, name, now) + fed += 1 + + return fed + + +print("Example 19") +from unittest.mock import DEFAULT + +with patch.multiple( + "__main__", + autospec=True, + get_food_period=DEFAULT, + get_animals=DEFAULT, + feed_animal=DEFAULT, +): + now_func = Mock(spec=datetime.now) + now_func.return_value = datetime(2024, 6, 5, 15, 45) + get_food_period.return_value = timedelta(hours=3) + get_animals.return_value = [ + ("Spot", datetime(2024, 6, 5, 11, 15)), + ("Fluffy", datetime(2024, 6, 5, 12, 30)), + ("Jojo", datetime(2024, 6, 5, 12, 45)), + ] + + +print("Example 20") + result = do_rounds(database, "Meerkat", now_func=now_func) + assert result == 2 + + get_food_period.assert_called_once_with(database, "Meerkat") + get_animals.assert_called_once_with(database, "Meerkat") + feed_animal.assert_has_calls( + [ + call(database, "Spot", now_func.return_value), + call(database, "Fluffy", now_func.return_value), + ], + any_order=True, + ) diff --git a/example_code/item_112.py b/example_code/item_112.py new file mode 100755 index 0000000..c707d69 --- /dev/null +++ b/example_code/item_112.py @@ -0,0 +1,168 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class ZooDatabase: + + def get_animals(self, species): + pass + + def get_food_period(self, species): + pass + + def feed_animal(self, name, when): + pass + + +print("Example 2") +from datetime import datetime + +def do_rounds(database, species, *, now_func=datetime.now): + now = now_func() + feeding_timedelta = database.get_food_period(species) + animals = database.get_animals(species) + fed = 0 + + for name, last_mealtime in animals: + if (now - last_mealtime) >= feeding_timedelta: + database.feed_animal(name, now) + fed += 1 + + return fed + + +print("Example 3") +from unittest.mock import Mock + +database = Mock(spec=ZooDatabase) +print(database.feed_animal) +database.feed_animal() +database.feed_animal.assert_any_call() + + +print("Example 4") +from datetime import timedelta +from unittest.mock import call + +now_func = Mock(spec=datetime.now) +now_func.return_value = datetime(2019, 6, 5, 15, 45) + +database = Mock(spec=ZooDatabase) +database.get_food_period.return_value = timedelta(hours=3) +database.get_animals.return_value = [ + ("Spot", datetime(2019, 6, 5, 11, 15)), + ("Fluffy", datetime(2019, 6, 5, 12, 30)), + ("Jojo", datetime(2019, 6, 5, 12, 55)), +] + + +print("Example 5") +result = do_rounds(database, "Meerkat", now_func=now_func) +assert result == 2 + +database.get_food_period.assert_called_once_with("Meerkat") +database.get_animals.assert_called_once_with("Meerkat") +database.feed_animal.assert_has_calls( + [ + call("Spot", now_func.return_value), + call("Fluffy", now_func.return_value), + ], + any_order=True, +) + + +print("Example 6") +try: + database.bad_method_name() +except: + logging.exception('Expected') +else: + assert False + + +print("Example 7") +DATABASE = None + +def get_database(): + global DATABASE + if DATABASE is None: + DATABASE = ZooDatabase() + return DATABASE + +def main(argv): + database = get_database() + species = argv[1] + count = do_rounds(database, species) + print(f"Fed {count} {species}(s)") + return 0 + + +print("Example 8") +import contextlib +import io +from unittest.mock import patch + +with patch("__main__.DATABASE", spec=ZooDatabase): + now = datetime.now() + + DATABASE.get_food_period.return_value = timedelta(hours=3) + DATABASE.get_animals.return_value = [ + ("Spot", now - timedelta(minutes=4.5)), + ("Fluffy", now - timedelta(hours=3.25)), + ("Jojo", now - timedelta(hours=3)), + ] + + fake_stdout = io.StringIO() + with contextlib.redirect_stdout(fake_stdout): + main(["program name", "Meerkat"]) + + found = fake_stdout.getvalue() + expected = "Fed 2 Meerkat(s)\n" + + assert found == expected diff --git a/example_code/item_113.py b/example_code/item_113.py new file mode 100755 index 0000000..e6f4bd1 --- /dev/null +++ b/example_code/item_113.py @@ -0,0 +1,97 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + import unittest + + class MyTestCase(unittest.TestCase): + def test_equal(self): + n = 5 + d = 3 + self.assertEqual(1.667, n / d) # Raises + + suite = unittest.defaultTestLoader.loadTestsFromTestCase(MyTestCase) + suite.debug() +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +print(5 / 3 * 0.1) +print(0.1 * 5 / 3) + + +print("Example 3") +class MyTestCase2(unittest.TestCase): + def test_equal(self): + n = 5 + d = 3 + self.assertAlmostEqual(1.667, n / d, places=2) # Changed + +suite = unittest.defaultTestLoader.loadTestsFromTestCase(MyTestCase2) +unittest.TextTestRunner(stream=STDOUT).run(suite) + + +print("Example 4") +print(1e24 / 1.1e16) +print(1e24 / 1.101e16) + + +print("Example 5") +class MyTestCase3(unittest.TestCase): + def test_equal(self): + a = 1e24 / 1.1e16 + b = 1e24 / 1.101e16 + self.assertAlmostEqual(90.9e6, a, delta=0.1e6) + self.assertAlmostEqual(90.9e6, b, delta=0.1e6) +suite = unittest.defaultTestLoader.loadTestsFromTestCase(MyTestCase3) +unittest.TextTestRunner(stream=STDOUT).run(suite) diff --git a/example_code/item_114/debugging/always_breakpoint.py b/example_code/item_114/debugging/always_breakpoint.py new file mode 100755 index 0000000..6649805 --- /dev/null +++ b/example_code/item_114/debugging/always_breakpoint.py @@ -0,0 +1,36 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math + +def compute_rmse(observed, ideal): + total_err_2 = 0 + count = 0 + for got, wanted in zip(observed, ideal): + err_2 = (got - wanted) ** 2 + breakpoint() # Start the debugger here + total_err_2 += err_2 + count += 1 + + mean_err = total_err_2 / count + rmse = math.sqrt(mean_err) + return rmse + +result = compute_rmse( + [1.8, 1.7, 3.2, 6], + [2, 1.5, 3, 5], +) +print(result) diff --git a/example_code/item_114/debugging/conditional_breakpoint.py b/example_code/item_114/debugging/conditional_breakpoint.py new file mode 100755 index 0000000..3a31f38 --- /dev/null +++ b/example_code/item_114/debugging/conditional_breakpoint.py @@ -0,0 +1,36 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math + +def compute_rmse(observed, ideal): + total_err_2 = 0 + count = 0 + for got, wanted in zip(observed, ideal): + err_2 = (got - wanted) ** 2 + if err_2 >= 1: # Start the debugger if True + breakpoint() + total_err_2 += err_2 + count += 1 + mean_err = total_err_2 / count + rmse = math.sqrt(mean_err) + return rmse + +result = compute_rmse( + [1.8, 1.7, 3.2, 7], + [2, 1.5, 3, 5], +) +print(result) diff --git a/example_code/item_114/debugging/my_module.py b/example_code/item_114/debugging/my_module.py new file mode 100755 index 0000000..dd55060 --- /dev/null +++ b/example_code/item_114/debugging/my_module.py @@ -0,0 +1,34 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math + + +def squared_error(point, mean): + err = point - mean + return err**2 + + +def compute_variance(data): + mean = sum(data) / len(data) + err_2_sum = sum(squared_error(x, mean) for x in data) + variance = err_2_sum / (len(data) - 1) + return variance + + +def compute_stddev(data): + variance = compute_variance(data) + return math.sqrt(variance) diff --git a/example_code/item_114/debugging/postmortem_breakpoint.py b/example_code/item_114/debugging/postmortem_breakpoint.py new file mode 100755 index 0000000..ab0f35e --- /dev/null +++ b/example_code/item_114/debugging/postmortem_breakpoint.py @@ -0,0 +1,35 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math + +def compute_rmse(observed, ideal): + total_err_2 = 0 + count = 0 + for got, wanted in zip(observed, ideal): + err_2 = (got - wanted) ** 2 + total_err_2 += err_2 + count += 1 + + mean_err = total_err_2 / count + rmse = math.sqrt(mean_err) + return rmse + +result = compute_rmse( + [1.8, 1.7, 3.2, 7j], # Bad input + [2, 1.5, 3, 5], +) +print(result) diff --git a/example_code/item_115/tracemalloc/top_n.py b/example_code/item_115/tracemalloc/top_n.py new file mode 100755 index 0000000..8a4abce --- /dev/null +++ b/example_code/item_115/tracemalloc/top_n.py @@ -0,0 +1,29 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import tracemalloc + +tracemalloc.start(10) # Set stack depth +time1 = tracemalloc.take_snapshot() # Before snapshot + +import waste_memory + +x = waste_memory.run() # Usage to debug +time2 = tracemalloc.take_snapshot() # After snapshot + +stats = time2.compare_to(time1, "lineno") # Compare snapshots +for stat in stats[:3]: + print(stat) diff --git a/example_code/item_115/tracemalloc/using_gc.py b/example_code/item_115/tracemalloc/using_gc.py new file mode 100755 index 0000000..39faa18 --- /dev/null +++ b/example_code/item_115/tracemalloc/using_gc.py @@ -0,0 +1,31 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import gc + +found_objects = gc.get_objects() +print("Before:", len(found_objects)) + +import waste_memory + +hold_reference = waste_memory.run() + +found_objects = gc.get_objects() +print("After: ", len(found_objects)) +for obj in found_objects[:3]: + print(repr(obj)[:100]) + +print("...") diff --git a/example_code/item_115/tracemalloc/waste_memory.py b/example_code/item_115/tracemalloc/waste_memory.py new file mode 100755 index 0000000..f545535 --- /dev/null +++ b/example_code/item_115/tracemalloc/waste_memory.py @@ -0,0 +1,35 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# waste_memory.py +import os + +class MyObject: + def __init__(self): + self.data = os.urandom(100) + +def get_data(): + values = [] + for _ in range(100): + obj = MyObject() + values.append(obj) + return values + +def run(): + deep_values = [] + for _ in range(100): + deep_values.append(get_data()) + return deep_values diff --git a/example_code/item_115/tracemalloc/with_trace.py b/example_code/item_115/tracemalloc/with_trace.py new file mode 100755 index 0000000..3e8d7a2 --- /dev/null +++ b/example_code/item_115/tracemalloc/with_trace.py @@ -0,0 +1,30 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import tracemalloc + +tracemalloc.start(10) +time1 = tracemalloc.take_snapshot() + +import waste_memory + +x = waste_memory.run() +time2 = tracemalloc.take_snapshot() + +stats = time2.compare_to(time1, "traceback") +top = stats[0] +print("Biggest offender is:") +print("\n".join(top.traceback.format())) diff --git a/example_code/item_118.py b/example_code/item_118.py new file mode 100755 index 0000000..dd27484 --- /dev/null +++ b/example_code/item_118.py @@ -0,0 +1,115 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def palindrome(word): + """Return True if the given word is a palindrome.""" + return word == word[::-1] + +assert palindrome("tacocat") +assert not palindrome("banana") + + +print("Example 2") +print(palindrome.__doc__) + + +print("Example 3") +"""Library for finding linguistic patterns in words. + +Testing how words relate to each other can be tricky sometimes! +This module provides easy ways to determine when words you've +found have special properties. + +Available functions: +- palindrome: Determine if a word is a palindrome. +- check_anagram: Determine if two words are anagrams. +... +""" + + +print("Example 4") +class Player: + """Represents a player of the game. + + Subclasses may override the 'tick' method to provide + custom animations for the player's movement depending + on their power level, etc. + + Public attributes: + - power: Unused power-ups (float between 0 and 1). + - coins: Coins found during the level (integer). + """ + + +print("Example 5") +import itertools + +def find_anagrams(word, dictionary): + """Find all anagrams for a word. + + This function only runs as fast as the test for + membership in the 'dictionary' container. + + Args: + word: String of the target word. + dictionary: collections.abc.Container with all + strings that are known to be actual words. + + Returns: + List of anagrams that were found. Empty if + none were found. + """ + permutations = itertools.permutations(word, len(word)) + possible = ("".join(x) for x in permutations) + found = {word for word in possible if word in dictionary} + return list(found) + + +assert find_anagrams("pancakes", ["scanpeak"]) == ["scanpeak"] diff --git a/example_code/item_118_example_06.py b/example_code/item_118_example_06.py new file mode 100755 index 0000000..3851c53 --- /dev/null +++ b/example_code/item_118_example_06.py @@ -0,0 +1,25 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 6") +# Check types in this file with: python -m mypy + +from collections.abc import Container + +def find_anagrams(word: str, dictionary: Container[str]) -> list[str]: + return [] diff --git a/example_code/item_118_example_07.py b/example_code/item_118_example_07.py new file mode 100755 index 0000000..b14be11 --- /dev/null +++ b/example_code/item_118_example_07.py @@ -0,0 +1,37 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 7") +# Check types in this file with: python -m mypy + +from collections.abc import Container + +def find_anagrams(word: str, dictionary: Container[str]) -> list[str]: + """Find all anagrams for a word. + + This function only runs as fast as the test for + membership in the 'dictionary' container. + + Args: + word: Target word. + dictionary: All known actual words. + + Returns: + Anagrams that were found. + """ + return [] diff --git a/example_code/item_119/api_package/api_consumer.py b/example_code/item_119/api_package/api_consumer.py new file mode 100755 index 0000000..da151bc --- /dev/null +++ b/example_code/item_119/api_package/api_consumer.py @@ -0,0 +1,32 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from mypackage import * + +a = Projectile(1.5, 3) +b = Projectile(4, 1.7) +after_a, after_b = simulate_collision(a, b) +print(after_a.__dict__, after_b.__dict__) + +import mypackage + +try: + mypackage._dot_product + assert False +except AttributeError: + pass # Expected + +mypackage.utils._dot_product # But this is defined diff --git a/example_code/item_119/api_package/main.py b/example_code/item_119/api_package/main.py new file mode 100755 index 0000000..2a2f9b4 --- /dev/null +++ b/example_code/item_119/api_package/main.py @@ -0,0 +1,17 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import mypackage.utils diff --git a/example_code/item_119/api_package/main2.py b/example_code/item_119/api_package/main2.py new file mode 100755 index 0000000..993afef --- /dev/null +++ b/example_code/item_119/api_package/main2.py @@ -0,0 +1,17 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from mypackage import utils diff --git a/example_code/item_119/api_package/mypackage/__init__.py b/example_code/item_119/api_package/mypackage/__init__.py new file mode 100755 index 0000000..54e681b --- /dev/null +++ b/example_code/item_119/api_package/mypackage/__init__.py @@ -0,0 +1,23 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__all__ = [] +from .models import * + +__all__ += models.__all__ +from .utils import * + +__all__ += utils.__all__ diff --git a/example_code/item_119/api_package/mypackage/models.py b/example_code/item_119/api_package/mypackage/models.py new file mode 100755 index 0000000..5b7b7ff --- /dev/null +++ b/example_code/item_119/api_package/mypackage/models.py @@ -0,0 +1,22 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__all__ = ["Projectile"] + +class Projectile: + def __init__(self, mass, velocity): + self.mass = mass + self.velocity = velocity diff --git a/example_code/item_119/api_package/mypackage/utils.py b/example_code/item_119/api_package/mypackage/utils.py new file mode 100755 index 0000000..327e0cd --- /dev/null +++ b/example_code/item_119/api_package/mypackage/utils.py @@ -0,0 +1,27 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .models import Projectile + +__all__ = ["simulate_collision"] + +def _dot_product(a, b): + pass + +def simulate_collision(a, b): + after_a = Projectile(-a.mass, -a.velocity) + after_b = Projectile(-b.mass, -b.velocity) + return after_a, after_b diff --git a/example_code/item_119/namespace_package/analysis/__init__.py b/example_code/item_119/namespace_package/analysis/__init__.py new file mode 100755 index 0000000..bb03669 --- /dev/null +++ b/example_code/item_119/namespace_package/analysis/__init__.py @@ -0,0 +1,17 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + diff --git a/example_code/item_119/namespace_package/analysis/utils.py b/example_code/item_119/namespace_package/analysis/utils.py new file mode 100755 index 0000000..a89126d --- /dev/null +++ b/example_code/item_119/namespace_package/analysis/utils.py @@ -0,0 +1,27 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math + + +def log_base2_bucket(value): + return math.log(value, 2) + + + + +def inspect(value): + pass diff --git a/example_code/item_119/namespace_package/frontend/__init__.py b/example_code/item_119/namespace_package/frontend/__init__.py new file mode 100755 index 0000000..bb03669 --- /dev/null +++ b/example_code/item_119/namespace_package/frontend/__init__.py @@ -0,0 +1,17 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + diff --git a/example_code/item_119/namespace_package/frontend/utils.py b/example_code/item_119/namespace_package/frontend/utils.py new file mode 100755 index 0000000..0419e89 --- /dev/null +++ b/example_code/item_119/namespace_package/frontend/utils.py @@ -0,0 +1,24 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def stringify(value): + return str(value) + + + + +def inspect(value): + pass diff --git a/example_code/item_119/namespace_package/main.py b/example_code/item_119/namespace_package/main.py new file mode 100755 index 0000000..4963b4e --- /dev/null +++ b/example_code/item_119/namespace_package/main.py @@ -0,0 +1,21 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from analysis.utils import log_base2_bucket +from frontend.utils import stringify + +bucket = stringify(log_base2_bucket(33)) +print(repr(bucket)) diff --git a/example_code/item_119/namespace_package/main2.py b/example_code/item_119/namespace_package/main2.py new file mode 100755 index 0000000..9071f42 --- /dev/null +++ b/example_code/item_119/namespace_package/main2.py @@ -0,0 +1,21 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from analysis.utils import inspect +from frontend.utils import inspect # Overwrites! + +"frontend" in inspect.__module__ +print(inspect.__module__) diff --git a/example_code/item_119/namespace_package/main3.py b/example_code/item_119/namespace_package/main3.py new file mode 100755 index 0000000..41b2380 --- /dev/null +++ b/example_code/item_119/namespace_package/main3.py @@ -0,0 +1,22 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from analysis.utils import inspect as analysis_inspect +from frontend.utils import inspect as frontend_inspect + +value = 33 +if analysis_inspect(value) == frontend_inspect(value): + print("Inspection equal!") diff --git a/example_code/item_119/namespace_package/main4.py b/example_code/item_119/namespace_package/main4.py new file mode 100755 index 0000000..99b16f8 --- /dev/null +++ b/example_code/item_119/namespace_package/main4.py @@ -0,0 +1,22 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import analysis.utils +import frontend.utils + +value = 33 +if analysis.utils.inspect(value) == frontend.utils.inspect(value): + print("Inspection equal!") diff --git a/example_code/item_120.py b/example_code/item_120.py new file mode 100755 index 0000000..f4e6da6 --- /dev/null +++ b/example_code/item_120.py @@ -0,0 +1,63 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 4") +# db_connection.py +import sys + +class Win32Database: + pass + +class PosixDatabase: + pass + +if sys.platform.startswith("win32"): + Database = Win32Database +else: + Database = PosixDatabase diff --git a/example_code/item_120/module_scope/db_connection.py b/example_code/item_120/module_scope/db_connection.py new file mode 100755 index 0000000..6db2ac5 --- /dev/null +++ b/example_code/item_120/module_scope/db_connection.py @@ -0,0 +1,29 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# db_connection.py +import __main__ + +class TestingDatabase: + pass + +class RealDatabase: + pass + +if __main__.TESTING: + Database = TestingDatabase +else: + Database = RealDatabase diff --git a/example_code/item_120/module_scope/dev_main.py b/example_code/item_120/module_scope/dev_main.py new file mode 100755 index 0000000..2044a89 --- /dev/null +++ b/example_code/item_120/module_scope/dev_main.py @@ -0,0 +1,21 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +TESTING = True + +import db_connection + +db = db_connection.Database() diff --git a/example_code/item_120/module_scope/prod_main.py b/example_code/item_120/module_scope/prod_main.py new file mode 100755 index 0000000..669078c --- /dev/null +++ b/example_code/item_120/module_scope/prod_main.py @@ -0,0 +1,21 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +TESTING = False + +import db_connection + +db = db_connection.Database() diff --git a/example_code/item_121.py b/example_code/item_121.py new file mode 100755 index 0000000..4d9f308 --- /dev/null +++ b/example_code/item_121.py @@ -0,0 +1,191 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +# my_module.py +def determine_weight(volume, density): + if density <= 0: + raise ValueError("Density must be positive") + + +try: + determine_weight(1, 0) +except ValueError: + pass +else: + assert False + + +print("Example 2") +# my_module.py +class Error(Exception): + """Base-class for all exceptions raised by this module.""" + +class InvalidDensityError(Error): + """There was a problem with a provided density value.""" + +class InvalidVolumeError(Error): + """There was a problem with the provided weight value.""" + +def determine_weight(volume, density): + if density < 0: + raise InvalidDensityError("Density must be positive") + if volume < 0: + raise InvalidVolumeError("Volume must be positive") + if volume == 0: + density / volume + + +print("Example 3") +class my_module: + Error = Error + InvalidDensityError = InvalidDensityError + + @staticmethod + def determine_weight(volume, density): + if density < 0: + raise InvalidDensityError("Density must be positive") + if volume < 0: + raise InvalidVolumeError("Volume must be positive") + if volume == 0: + density / volume + +try: + weight = my_module.determine_weight(1, -1) +except my_module.Error: + logging.exception("Unexpected error") +else: + assert False + + +print("Example 4") +SENTINEL = object() +weight = SENTINEL +try: + weight = my_module.determine_weight(-1, 1) +except my_module.InvalidDensityError: + weight = 0 +except my_module.Error: + logging.exception("Bug in the calling code") +else: + assert False + +assert weight is SENTINEL + + +print("Example 5") +try: + weight = SENTINEL + try: + weight = my_module.determine_weight(0, 1) + except my_module.InvalidDensityError: + weight = 0 + except my_module.Error: + logging.exception("Bug in the calling code") + except Exception: + logging.exception("Bug in the API code!") + raise # Re-raise exception to the caller + else: + assert False + + assert weight == 0 +except: + logging.exception('Expected') +else: + assert False + + +print("Example 6") +# my_module.py + +class NegativeDensityError(InvalidDensityError): + """A provided density value was negative.""" + + +def determine_weight(volume, density): + if density < 0: + raise NegativeDensityError("Density must be positive") + + +print("Example 7") +try: + my_module.NegativeDensityError = NegativeDensityError + my_module.determine_weight = determine_weight + try: + weight = my_module.determine_weight(1, -1) + except my_module.NegativeDensityError: + raise ValueError("Must supply non-negative density") + except my_module.InvalidDensityError: + weight = 0 + except my_module.Error: + logging.exception("Bug in the calling code") + except Exception: + logging.exception("Bug in the API code!") + raise + else: + assert False +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +# my_module.py +class Error(Exception): + """Base-class for all exceptions raised by this module.""" + +class WeightError(Error): + """Base-class for weight calculation errors.""" + +class VolumeError(Error): + """Base-class for volume calculation errors.""" + +class DensityError(Error): + """Base-class for density calculation errors.""" diff --git a/example_code/item_122/recursive_import_bad/app.py b/example_code/item_122/recursive_import_bad/app.py new file mode 100755 index 0000000..b0dd91e --- /dev/null +++ b/example_code/item_122/recursive_import_bad/app.py @@ -0,0 +1,24 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import dialog + +class Prefs: + def get(self, name): + pass + +prefs = Prefs() +dialog.show() diff --git a/example_code/item_122/recursive_import_bad/dialog.py b/example_code/item_122/recursive_import_bad/dialog.py new file mode 100755 index 0000000..b98cc57 --- /dev/null +++ b/example_code/item_122/recursive_import_bad/dialog.py @@ -0,0 +1,27 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import app + +class Dialog: + def __init__(self, save_dir): + self.save_dir = save_dir + + +save_dialog = Dialog(app.prefs.get("save_dir")) + +def show(): + print("Showing the dialog!") diff --git a/example_code/item_122/recursive_import_bad/main.py b/example_code/item_122/recursive_import_bad/main.py new file mode 100755 index 0000000..bf57d21 --- /dev/null +++ b/example_code/item_122/recursive_import_bad/main.py @@ -0,0 +1,17 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import app diff --git a/example_code/item_122/recursive_import_dynamic/app.py b/example_code/item_122/recursive_import_dynamic/app.py new file mode 100755 index 0000000..46aca3d --- /dev/null +++ b/example_code/item_122/recursive_import_dynamic/app.py @@ -0,0 +1,25 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import dialog + +class Prefs: + def get(self, name): + pass + + +prefs = Prefs() +dialog.show() diff --git a/example_code/item_122/recursive_import_dynamic/dialog.py b/example_code/item_122/recursive_import_dynamic/dialog.py new file mode 100755 index 0000000..2824cea --- /dev/null +++ b/example_code/item_122/recursive_import_dynamic/dialog.py @@ -0,0 +1,33 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reenabling this will break things. +# import app + +class Dialog: + def __init__(self): + pass + + +# Using this instead will break things +# save_dialog = Dialog(app.prefs.get('save_dir')) +save_dialog = Dialog() + +def show(): + import app # Dynamic import + + save_dialog.save_dir = app.prefs.get("save_dir") + print("Showing the dialog!") diff --git a/example_code/item_122/recursive_import_dynamic/main.py b/example_code/item_122/recursive_import_dynamic/main.py new file mode 100755 index 0000000..bf57d21 --- /dev/null +++ b/example_code/item_122/recursive_import_dynamic/main.py @@ -0,0 +1,17 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import app diff --git a/example_code/item_122/recursive_import_nosideeffects/app.py b/example_code/item_122/recursive_import_nosideeffects/app.py new file mode 100755 index 0000000..10734f2 --- /dev/null +++ b/example_code/item_122/recursive_import_nosideeffects/app.py @@ -0,0 +1,27 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import dialog + +class Prefs: + def get(self, name): + pass + + +prefs = Prefs() + +def configure(): + pass diff --git a/example_code/item_122/recursive_import_nosideeffects/dialog.py b/example_code/item_122/recursive_import_nosideeffects/dialog.py new file mode 100755 index 0000000..0138142 --- /dev/null +++ b/example_code/item_122/recursive_import_nosideeffects/dialog.py @@ -0,0 +1,30 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import app + +class Dialog: + def __init__(self): + pass + + +save_dialog = Dialog() + +def show(): + print("Showing the dialog!") + +def configure(): + save_dialog.save_dir = app.prefs.get("save_dir") diff --git a/example_code/item_122/recursive_import_nosideeffects/main.py b/example_code/item_122/recursive_import_nosideeffects/main.py new file mode 100755 index 0000000..7a39f76 --- /dev/null +++ b/example_code/item_122/recursive_import_nosideeffects/main.py @@ -0,0 +1,23 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import app +import dialog + +app.configure() +dialog.configure() + +dialog.show() diff --git a/example_code/item_122/recursive_import_ordering/app.py b/example_code/item_122/recursive_import_ordering/app.py new file mode 100755 index 0000000..12b9e5f --- /dev/null +++ b/example_code/item_122/recursive_import_ordering/app.py @@ -0,0 +1,26 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class Prefs: + def get(self, name): + pass + + +prefs = Prefs() + +import dialog # Moved + +dialog.show() diff --git a/example_code/item_122/recursive_import_ordering/dialog.py b/example_code/item_122/recursive_import_ordering/dialog.py new file mode 100755 index 0000000..f36cb22 --- /dev/null +++ b/example_code/item_122/recursive_import_ordering/dialog.py @@ -0,0 +1,30 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import app + + +class Dialog: + def __init__(self, save_dir): + self.save_dir = save_dir + + + +save_dialog = Dialog(app.prefs.get("save_dir")) + + +def show(): + print("Showing the dialog!") diff --git a/example_code/item_122/recursive_import_ordering/main.py b/example_code/item_122/recursive_import_ordering/main.py new file mode 100755 index 0000000..bf57d21 --- /dev/null +++ b/example_code/item_122/recursive_import_ordering/main.py @@ -0,0 +1,17 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import app diff --git a/example_code/item_123.py b/example_code/item_123.py new file mode 100755 index 0000000..be85f79 --- /dev/null +++ b/example_code/item_123.py @@ -0,0 +1,276 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def print_distance(speed, duration): + distance = speed * duration + print(f"{distance} miles") + +print_distance(5, 2.5) + + +print("Example 2") +print_distance(1000, 3) + + +print("Example 3") +CONVERSIONS = { + "mph": 1.60934 / 3600 * 1000, # m/s + "hours": 3600, # seconds + "miles": 1.60934 * 1000, # m + "meters": 1, # m + "m/s": 1, # m/s + "seconds": 1, # s +} + +def convert(value, units): + rate = CONVERSIONS[units] + return rate * value + +def localize(value, units): + rate = CONVERSIONS[units] + return value / rate + +def print_distance( + speed, + duration, + *, + speed_units="mph", + time_units="hours", + distance_units="miles", +): + norm_speed = convert(speed, speed_units) + norm_duration = convert(duration, time_units) + norm_distance = norm_speed * norm_duration + distance = localize(norm_distance, distance_units) + print(f"{distance} {distance_units}") + + +print("Example 4") +print_distance( + 1000, + 3, + speed_units="meters", + time_units="seconds", +) + + +print("Example 5") +import warnings + +def print_distance( + speed, + duration, + *, + speed_units=None, + time_units=None, + distance_units=None, +): + if speed_units is None: + warnings.warn( + "speed_units required", + DeprecationWarning, + ) + speed_units = "mph" + + if time_units is None: + warnings.warn( + "time_units required", + DeprecationWarning, + ) + time_units = "hours" + + if distance_units is None: + warnings.warn( + "distance_units required", + DeprecationWarning, + ) + distance_units = "miles" + + norm_speed = convert(speed, speed_units) + norm_duration = convert(duration, time_units) + norm_distance = norm_speed * norm_duration + distance = localize(norm_distance, distance_units) + print(f"{distance} {distance_units}") + + +print("Example 6") +import contextlib +import io + +fake_stderr = io.StringIO() +with contextlib.redirect_stderr(fake_stderr): + print_distance( + 1000, + 3, + speed_units="meters", + time_units="seconds", + ) + +print(fake_stderr.getvalue()) + + +print("Example 7") +def require(name, value, default): + if value is not None: + return value + warnings.warn( + f"{name} will be required soon, update your code", + DeprecationWarning, + stacklevel=3, + ) + return default + +def print_distance( + speed, + duration, + *, + speed_units=None, + time_units=None, + distance_units=None, +): + speed_units = require( + "speed_units", + speed_units, + "mph", + ) + time_units = require( + "time_units", + time_units, + "hours", + ) + distance_units = require( + "distance_units", + distance_units, + "miles", + ) + + norm_speed = convert(speed, speed_units) + norm_duration = convert(duration, time_units) + norm_distance = norm_speed * norm_duration + distance = localize(norm_distance, distance_units) + print(f"{distance} {distance_units}") + + +print("Example 8") +import contextlib +import io + +fake_stderr = io.StringIO() +with contextlib.redirect_stderr(fake_stderr): + print_distance( + 1000, + 3, + speed_units="meters", + time_units="seconds", + ) + +print(fake_stderr.getvalue()) + + +print("Example 9") +warnings.simplefilter("error") +try: + warnings.warn( + "This usage is deprecated", + DeprecationWarning, + ) +except DeprecationWarning: + pass # Expected +else: + assert False + +warnings.resetwarnings() + + +print("Example 10") +warnings.resetwarnings() + +warnings.simplefilter("ignore") +warnings.warn("This will not be printed to stderr") + +warnings.resetwarnings() + + +print("Example 11") +import logging + +fake_stderr = io.StringIO() +handler = logging.StreamHandler(fake_stderr) +formatter = logging.Formatter("%(asctime)-15s WARNING] %(message)s") +handler.setFormatter(formatter) + +logging.captureWarnings(True) +logger = logging.getLogger("py.warnings") +logger.addHandler(handler) +logger.setLevel(logging.DEBUG) + +warnings.resetwarnings() +warnings.simplefilter("default") +warnings.warn("This will go to the logs output") + +print(fake_stderr.getvalue()) + +warnings.resetwarnings() + + +print("Example 12") +with warnings.catch_warnings(record=True) as found_warnings: + found = require("my_arg", None, "fake units") + expected = "fake units" + assert found == expected + + +print("Example 13") +assert len(found_warnings) == 1 +single_warning = found_warnings[0] +assert str(single_warning.message) == ( + "my_arg will be required soon, update your code" +) +assert single_warning.category == DeprecationWarning diff --git a/example_code/item_124.py b/example_code/item_124.py new file mode 100755 index 0000000..7510fda --- /dev/null +++ b/example_code/item_124.py @@ -0,0 +1,180 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + def subtract(a, b): + return a - b + + subtract(10, "5") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +class Counter: + def __init__(self): + self.value = 0 + + def add(self, offset): + value += offset + + def get(self) -> int: + self.value + + +print("Example 4") +try: + counter = Counter() + counter.add(5) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +try: + counter = Counter() + found = counter.get() + assert found == 0, found +except: + logging.exception('Expected') +else: + assert False + + +print("Example 7") +try: + def combine(func, values): + assert len(values) > 0 + + result = values[0] + for next_value in values[1:]: + result = func(result, next_value) + + return result + + def add(x, y): + return x + y + + inputs = [1, 2, 3, 4j] + result = combine(add, inputs) + assert result == 10, result # Fails +except: + logging.exception('Expected') +else: + assert False + + +print("Example 9") +try: + def get_or_default(value, default): + if value is not None: + return value + return value + + found = get_or_default(3, 5) + assert found == 3 + + found = get_or_default(None, 5) + assert found == 5, found # Fails +except: + logging.exception('Expected') +else: + assert False + + +print("Example 11") +class FirstClass: + def __init__(self, value): + self.value = value + +class SecondClass: + def __init__(self, value): + self.value = value + +second = SecondClass(5) +first = FirstClass(second) + +del FirstClass +del SecondClass + + +print("Example 13") +try: + class FirstClass: + def __init__(self, value: SecondClass) -> None: # Breaks + self.value = value + + class SecondClass: + def __init__(self, value: int) -> None: + self.value = value + + second = SecondClass(5) + first = FirstClass(second) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 14") +class FirstClass: + def __init__(self, value: "SecondClass") -> None: # OK + self.value = value + +class SecondClass: + def __init__(self, value: int) -> None: + self.value = value + +second = SecondClass(5) +first = FirstClass(second) diff --git a/example_code/item_124_example_02.py b/example_code/item_124_example_02.py new file mode 100755 index 0000000..e370679 --- /dev/null +++ b/example_code/item_124_example_02.py @@ -0,0 +1,25 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 2") +# Check types in this file with: python -m mypy + +def subtract(a: int, b: int) -> int: # Function annotation + return a - b + +subtract(10, "5") # Oops: passed string value diff --git a/example_code/item_124_example_06.py b/example_code/item_124_example_06.py new file mode 100755 index 0000000..e6d5151 --- /dev/null +++ b/example_code/item_124_example_06.py @@ -0,0 +1,35 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 6") +# Check types in this file with: python -m mypy + +class Counter: + def __init__(self) -> None: + self.value: int = 0 # Field / variable annotation + + def add(self, offset: int) -> None: + value += offset # Oops: forgot "self." + + def get(self) -> int: + self.value # Oops: forgot "return" + +counter = Counter() +counter.add(5) +counter.add(3) +assert counter.get() == 8 diff --git a/example_code/item_124_example_08.py b/example_code/item_124_example_08.py new file mode 100755 index 0000000..f7b25cd --- /dev/null +++ b/example_code/item_124_example_08.py @@ -0,0 +1,44 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 8") +# Check types in this file with: python -m mypy + +from collections.abc import Callable +from typing import TypeVar + +Value = TypeVar("Value") +Func = Callable[[Value, Value], Value] + +def combine(func: Func[Value], values: list[Value]) -> Value: + assert len(values) > 0 + + result = values[0] + for next_value in values[1:]: + result = func(result, next_value) + + return result + +Real = TypeVar("Real", int, float) + +def add(x: Real, y: Real) -> Real: + return x + y + +inputs = [1, 2, 3, 4j] # Oops: included a complex number +result = combine(add, inputs) +assert result == 10 diff --git a/example_code/item_124_example_10.py b/example_code/item_124_example_10.py new file mode 100755 index 0000000..6f9afda --- /dev/null +++ b/example_code/item_124_example_10.py @@ -0,0 +1,25 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 10") +# Check types in this file with: python -m mypy + +def get_or_default(value: int | None, default: int) -> int: + if value is not None: + return value + return value # Oops: should have returned "default" diff --git a/example_code/item_124_example_12.py b/example_code/item_124_example_12.py new file mode 100755 index 0000000..78fbfed --- /dev/null +++ b/example_code/item_124_example_12.py @@ -0,0 +1,31 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 12") +# Check types in this file with: python3 -m mypy + +class FirstClass: + def __init__(self, value: SecondClass) -> None: + self.value = value + +class SecondClass: + def __init__(self, value: int) -> None: + self.value = value + +second = SecondClass(5) +first = FirstClass(second) diff --git a/example_code/item_125/zipimport_examples/django_pkgutil.py b/example_code/item_125/zipimport_examples/django_pkgutil.py new file mode 100755 index 0000000..5029358 --- /dev/null +++ b/example_code/item_125/zipimport_examples/django_pkgutil.py @@ -0,0 +1,24 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# django_pkgutil.py +import pkgutil + +data = pkgutil.get_data( + "django.conf.locale", + "en/LC_MESSAGES/django.po", +) +print(data.decode("utf-8")) diff --git a/example_code/item_125/zipimport_examples/trans_real.py b/example_code/item_125/zipimport_examples/trans_real.py new file mode 100755 index 0000000..5b78b7d --- /dev/null +++ b/example_code/item_125/zipimport_examples/trans_real.py @@ -0,0 +1,29 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# trans_real.py +# Copyright (c) Django Software Foundation and +# individual contributors. All rights reserved. +class DjangoTranslation(gettext_module.GNUTranslations): + + def _init_translation_catalog(self): + settingsfile = sys.modules[settings.__module__].__file__ + localedir = os.path.join( + os.path.dirname(settingsfile), + "locale", + ) + translation = self._new_gnu_trans(localedir) + self.merge(translation) From dcf7aa0a56026fc00531dae7215693044429b1dc Mon Sep 17 00:00:00 2001 From: Brett Slatkin Date: Tue, 10 Dec 2024 15:11:22 -0800 Subject: [PATCH 10/12] Small bug fixes --- example_code/item_025_example_17.py | 2 +- example_code/item_032_example_09.py | 2 +- example_code/item_036_example_09.py | 2 +- example_code/item_051_example_04.py | 2 +- example_code/item_051_example_05.py | 2 +- example_code/item_056_example_11.py | 2 +- example_code/item_056_example_12.py | 2 +- example_code/item_059.py | 4 ++-- example_code/item_060.py | 4 ++-- example_code/item_064.py | 2 +- example_code/item_065.py | 2 +- example_code/item_070.py | 6 +++--- example_code/item_076.py | 32 +++++++++++++++-------------- example_code/item_078.py | 6 +++--- example_code/item_111.py | 2 +- example_code/item_118_example_06.py | 2 +- example_code/item_118_example_07.py | 2 +- example_code/item_124_example_02.py | 2 +- example_code/item_124_example_06.py | 2 +- example_code/item_124_example_08.py | 2 +- example_code/item_124_example_10.py | 2 +- 21 files changed, 43 insertions(+), 41 deletions(-) diff --git a/example_code/item_025_example_17.py b/example_code/item_025_example_17.py index 9cdb383..cb6f973 100755 --- a/example_code/item_025_example_17.py +++ b/example_code/item_025_example_17.py @@ -17,7 +17,7 @@ print("Example 17") -# Check types in this file with: python -m mypy +# Check types in this file with: python3 -m mypy from typing import Dict, MutableMapping diff --git a/example_code/item_032_example_09.py b/example_code/item_032_example_09.py index c1cee75..a7c1648 100755 --- a/example_code/item_032_example_09.py +++ b/example_code/item_032_example_09.py @@ -17,7 +17,7 @@ print("Example 9") -# Check types in this file with: python -m mypy +# Check types in this file with: python3 -m mypy def careful_divide(a: float, b: float) -> float: """Divides a by b. diff --git a/example_code/item_036_example_09.py b/example_code/item_036_example_09.py index 2e4164d..2896986 100755 --- a/example_code/item_036_example_09.py +++ b/example_code/item_036_example_09.py @@ -17,7 +17,7 @@ print("Example 9") -# Check types in this file with: python -m mypy +# Check types in this file with: python3 -m mypy from datetime import datetime from time import sleep diff --git a/example_code/item_051_example_04.py b/example_code/item_051_example_04.py index eb8f3db..3d9fd9f 100755 --- a/example_code/item_051_example_04.py +++ b/example_code/item_051_example_04.py @@ -17,7 +17,7 @@ print("Example 4") -# Check types in this file with: python -m mypy +# Check types in this file with: python3 -m mypy from dataclasses import dataclass diff --git a/example_code/item_051_example_05.py b/example_code/item_051_example_05.py index 16ee66f..1d51199 100755 --- a/example_code/item_051_example_05.py +++ b/example_code/item_051_example_05.py @@ -17,7 +17,7 @@ print("Example 5") -# Check types in this file with: python -m mypy +# Check types in this file with: python3 -m mypy class RGB: def __init__( diff --git a/example_code/item_056_example_11.py b/example_code/item_056_example_11.py index 90fec69..eba031e 100755 --- a/example_code/item_056_example_11.py +++ b/example_code/item_056_example_11.py @@ -17,7 +17,7 @@ print("Example 11") -# Check types in this file with: python -m mypy +# Check types in this file with: python3 -m mypy from dataclasses import dataclass diff --git a/example_code/item_056_example_12.py b/example_code/item_056_example_12.py index 3423cf5..867579e 100755 --- a/example_code/item_056_example_12.py +++ b/example_code/item_056_example_12.py @@ -17,7 +17,7 @@ print("Example 12") -# Check types in this file with: python -m mypy +# Check types in this file with: python3 -m mypy from typing import Any, Final, Never diff --git a/example_code/item_059.py b/example_code/item_059.py index 05d1b6b..267a8f7 100755 --- a/example_code/item_059.py +++ b/example_code/item_059.py @@ -123,13 +123,13 @@ def __repr__(self): ) -print("Example 8") + print("Example 8") @property def quota(self): return self.max_quota - self.quota_consumed -print("Example 9") + print("Example 9") @quota.setter def quota(self, amount): delta = self.max_quota - amount diff --git a/example_code/item_060.py b/example_code/item_060.py index f361eb0..a1bf196 100755 --- a/example_code/item_060.py +++ b/example_code/item_060.py @@ -81,7 +81,7 @@ def _check_grade(value): raise ValueError("Grade must be between 0 and 100") -print("Example 4") + print("Example 4") @property def writing_grade(self): return self._writing_grade @@ -208,7 +208,7 @@ def __set_name__(self, owner, name): self.internal_name = "_" + name -print("Example 15") + print("Example 15") def __get__(self, instance, instance_type): if instance is None: return self diff --git a/example_code/item_064.py b/example_code/item_064.py index 0627cee..5c2624e 100755 --- a/example_code/item_064.py +++ b/example_code/item_064.py @@ -54,7 +54,7 @@ def __init__(self, column_name): self.internal_name = "_" + self.column_name -print("Example 2") + print("Example 2") def __get__(self, instance, instance_type): if instance is None: return self diff --git a/example_code/item_065.py b/example_code/item_065.py index 4cc5e07..d7175f9 100755 --- a/example_code/item_065.py +++ b/example_code/item_065.py @@ -278,7 +278,7 @@ def __init_subclass__(cls): cls.steps = tuple(steps) -print("Example 18") + print("Example 18") def run(self): for step_name in type(self).steps: func = getattr(self, step_name) diff --git a/example_code/item_070.py b/example_code/item_070.py index 352d3c7..351a99f 100755 --- a/example_code/item_070.py +++ b/example_code/item_070.py @@ -68,13 +68,13 @@ def __init__(self): self.lock = Lock() -print("Example 3") + print("Example 3") def put(self, item): with self.lock: self.items.append(item) -print("Example 4") + print("Example 4") def get(self): with self.lock: return self.items.popleft() @@ -94,7 +94,7 @@ def __init__(self, func, in_queue, out_queue): self.work_done = 0 -print("Example 6") + print("Example 6") def run(self): while True: self.polled_count += 1 diff --git a/example_code/item_076.py b/example_code/item_076.py index 875a013..46a1693 100755 --- a/example_code/item_076.py +++ b/example_code/item_076.py @@ -86,7 +86,7 @@ def __init__(self, *args): self.clear_state() -print("Example 3") + print("Example 3") def loop(self): while command := self.receive(): match command.split(" "): @@ -102,14 +102,14 @@ def loop(self): raise UnknownCommandError(command) -print("Example 4") + print("Example 4") def set_params(self, lower, upper): self.clear_state() self.lower = int(lower) self.upper = int(upper) -print("Example 5") + print("Example 5") def next_guess(self): if self.secret is not None: return self.secret @@ -125,7 +125,7 @@ def send_number(self): self.send(format(guess)) -print("Example 6") + print("Example 6") def receive_report(self, decision): last = self.guesses[-1] if decision == CORRECT: @@ -134,7 +134,7 @@ def receive_report(self, decision): print(f"Server: {last} is {decision}") -print("Example 7") + print("Example 7") def clear_state(self): self.lower = None self.upper = None @@ -144,6 +144,7 @@ def clear_state(self): print("Example 8") import contextlib +import time @contextlib.contextmanager def new_game(connection, lower, upper, secret): @@ -175,14 +176,14 @@ def __init__(self, send, receive, secret): self.last_distance = None -print("Example 10") + print("Example 10") def request_number(self): self.send("NUMBER") data = self.receive() return int(data) -print("Example 11") + print("Example 11") def report_outcome(self, number): new_distance = math.fabs(number - self.secret) @@ -203,7 +204,7 @@ def report_outcome(self, number): return decision -print("Example 12") + print("Example 12") def __iter__(self): while True: number = self.request_number() @@ -307,7 +308,7 @@ def __init__(self, *args): self.clear_state() -print("Example 18") + print("Example 18") async def loop(self): # Changed while command := await self.receive(): # Changed match command.split(" "): @@ -323,14 +324,14 @@ async def loop(self): # Changed raise UnknownCommandError(command) -print("Example 19") + print("Example 19") def set_params(self, lower, upper): self.clear_state() self.lower = int(lower) self.upper = int(upper) -print("Example 20") + print("Example 20") def next_guess(self): if self.secret is not None: return self.secret @@ -339,13 +340,14 @@ def next_guess(self): guess = random.randint(self.lower, self.upper) if guess not in self.guesses: return guess + async def send_number(self): # Changed guess = self.next_guess() self.guesses.append(guess) await self.send(format(guess)) # Changed -print("Example 21") + print("Example 21") def receive_report(self, decision): last = self.guesses[-1] if decision == CORRECT: @@ -390,14 +392,14 @@ def __init__(self, send, receive, secret): self.last_distance = None -print("Example 24") + print("Example 24") async def request_number(self): await self.send("NUMBER") # Changed data = await self.receive() # Changed return int(data) -print("Example 25") + print("Example 25") async def report_outcome(self, number): # Changed new_distance = math.fabs(number - self.secret) @@ -418,7 +420,7 @@ async def report_outcome(self, number): # Changed return decision -print("Example 26") + print("Example 26") async def __aiter__(self): # Changed while True: number = await self.request_number() # Changed diff --git a/example_code/item_078.py b/example_code/item_078.py index e0f2b30..43d67ba 100755 --- a/example_code/item_078.py +++ b/example_code/item_078.py @@ -111,7 +111,7 @@ def run(self): self.loop.run_until_complete(asyncio.sleep(0)) -print("Example 5") + print("Example 5") async def real_write(self, data): self.output.write(data) @@ -122,7 +122,7 @@ async def write(self, data): await asyncio.wrap_future(future) -print("Example 6") + print("Example 6") async def real_stop(self): self.loop.stop() @@ -133,7 +133,7 @@ async def stop(self): await asyncio.wrap_future(future) -print("Example 7") + print("Example 7") async def __aenter__(self): loop = asyncio.get_event_loop() await loop.run_in_executor(None, self.start) diff --git a/example_code/item_111.py b/example_code/item_111.py index 67099d7..2ce0c85 100755 --- a/example_code/item_111.py +++ b/example_code/item_111.py @@ -308,7 +308,7 @@ def do_rounds(database, species, *, now_func=datetime.now): ] -print("Example 20") + print("Example 20") result = do_rounds(database, "Meerkat", now_func=now_func) assert result == 2 diff --git a/example_code/item_118_example_06.py b/example_code/item_118_example_06.py index 3851c53..a1b101a 100755 --- a/example_code/item_118_example_06.py +++ b/example_code/item_118_example_06.py @@ -17,7 +17,7 @@ print("Example 6") -# Check types in this file with: python -m mypy +# Check types in this file with: python3 -m mypy from collections.abc import Container diff --git a/example_code/item_118_example_07.py b/example_code/item_118_example_07.py index b14be11..cfe7598 100755 --- a/example_code/item_118_example_07.py +++ b/example_code/item_118_example_07.py @@ -17,7 +17,7 @@ print("Example 7") -# Check types in this file with: python -m mypy +# Check types in this file with: python3 -m mypy from collections.abc import Container diff --git a/example_code/item_124_example_02.py b/example_code/item_124_example_02.py index e370679..5152d85 100755 --- a/example_code/item_124_example_02.py +++ b/example_code/item_124_example_02.py @@ -17,7 +17,7 @@ print("Example 2") -# Check types in this file with: python -m mypy +# Check types in this file with: python3 -m mypy def subtract(a: int, b: int) -> int: # Function annotation return a - b diff --git a/example_code/item_124_example_06.py b/example_code/item_124_example_06.py index e6d5151..9dd5763 100755 --- a/example_code/item_124_example_06.py +++ b/example_code/item_124_example_06.py @@ -17,7 +17,7 @@ print("Example 6") -# Check types in this file with: python -m mypy +# Check types in this file with: python3 -m mypy class Counter: def __init__(self) -> None: diff --git a/example_code/item_124_example_08.py b/example_code/item_124_example_08.py index f7b25cd..d27afe6 100755 --- a/example_code/item_124_example_08.py +++ b/example_code/item_124_example_08.py @@ -17,7 +17,7 @@ print("Example 8") -# Check types in this file with: python -m mypy +# Check types in this file with: python3 -m mypy from collections.abc import Callable from typing import TypeVar diff --git a/example_code/item_124_example_10.py b/example_code/item_124_example_10.py index 6f9afda..15a9efb 100755 --- a/example_code/item_124_example_10.py +++ b/example_code/item_124_example_10.py @@ -17,7 +17,7 @@ print("Example 10") -# Check types in this file with: python -m mypy +# Check types in this file with: python3 -m mypy def get_or_default(value: int | None, default: int) -> int: if value is not None: From f1e59df838ba2c807e6df209b697c207fa1b56d2 Mon Sep 17 00:00:00 2001 From: Brett Slatkin Date: Tue, 10 Dec 2024 15:38:09 -0800 Subject: [PATCH 11/12] Fixing C dependencies --- .gitignore | 11 +++ example_code/item_095.py | 5 +- example_code/item_095/my_library/my_library.c | 26 +++++++ example_code/item_095/my_library/my_library.h | 17 ++++ .../item_095/my_library/my_library_test.c | 52 +++++++++++++ .../item_096/my_extension/dot_product.c | 56 +++++++++++++ example_code/item_096/my_extension/init.c | 46 +++++++++++ .../item_096/my_extension/my_extension.h | 20 +++++ .../item_096/my_extension2/dot_product.c | 78 +++++++++++++++++++ example_code/item_096/my_extension2/init.c | 46 +++++++++++ .../item_096/my_extension2/my_extension2.h | 20 +++++ example_code/item_096/my_extension2/setup.py | 27 +++++++ 12 files changed, 400 insertions(+), 4 deletions(-) create mode 100755 example_code/item_095/my_library/my_library.c create mode 100755 example_code/item_095/my_library/my_library.h create mode 100755 example_code/item_095/my_library/my_library_test.c create mode 100644 example_code/item_096/my_extension/dot_product.c create mode 100644 example_code/item_096/my_extension/init.c create mode 100644 example_code/item_096/my_extension/my_extension.h create mode 100644 example_code/item_096/my_extension2/dot_product.c create mode 100644 example_code/item_096/my_extension2/init.c create mode 100644 example_code/item_096/my_extension2/my_extension2.h create mode 100644 example_code/item_096/my_extension2/setup.py diff --git a/.gitignore b/.gitignore index fbfa7d1..d269399 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,12 @@ **/*.pyc +__pycache__ +.mypy_cache +lib +bin +include +pyvenv.cfg +*.egg-info +*.so +*.lib +build + diff --git a/example_code/item_095.py b/example_code/item_095.py index e9a0472..5f36d14 100755 --- a/example_code/item_095.py +++ b/example_code/item_095.py @@ -65,10 +65,7 @@ def dot_product(a, b): import pathlib run_py = pathlib.Path(__file__) -tools_dir = run_py.parent -root_dir = tools_dir.parent -# TODO: Fix this for example code -library_path = root_dir / "Ch11" / "my_library" / "my_library.lib" +library_path = run_py.parent / "item_095" / "my_library" / "my_library.lib" my_library = ctypes.cdll.LoadLibrary(library_path) diff --git a/example_code/item_095/my_library/my_library.c b/example_code/item_095/my_library/my_library.c new file mode 100755 index 0000000..e52e63e --- /dev/null +++ b/example_code/item_095/my_library/my_library.c @@ -0,0 +1,26 @@ +/* + * Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "my_library.h" + +double dot_product(int length, double* a, double* b) { + double result = 0; + for (int i = 0; i < length; i++) { + result += a[i] * b[i]; + } + return result; +} diff --git a/example_code/item_095/my_library/my_library.h b/example_code/item_095/my_library/my_library.h new file mode 100755 index 0000000..a816bd5 --- /dev/null +++ b/example_code/item_095/my_library/my_library.h @@ -0,0 +1,17 @@ +/* + * Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +extern double dot_product(int length, double* a, double* b); diff --git a/example_code/item_095/my_library/my_library_test.c b/example_code/item_095/my_library/my_library_test.c new file mode 100755 index 0000000..2e98775 --- /dev/null +++ b/example_code/item_095/my_library/my_library_test.c @@ -0,0 +1,52 @@ +/* + * Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include "my_library.h" + + +int main(int argc, char** argv) { + { + double a[] = {3, 4, 5}; + double b[] = {-1, 9, -2.5}; + double found = dot_product(3, a, b); + double expected = 20.5; + printf("Found %f, Expected %f\n", found, expected); + assert(fabs(found - expected) < 0.01); + } + + { + double a[] = {0, 0, 0}; + double b[] = {1, 1, 1}; + double found = dot_product(3, a, b); + double expected = 0; + printf("Found %f, Expected %f\n", found, expected); + assert(fabs(found - expected) < 0.01); + } + + { + double a[] = {-1, -1, -1}; + double b[] = {1, 1, 1}; + double found = dot_product(3, a, b); + double expected = -3; + printf("Found %f, Expected %f\n", found, expected); + assert(fabs(found - expected) < 0.01); + } + + return 0; +} diff --git a/example_code/item_096/my_extension/dot_product.c b/example_code/item_096/my_extension/dot_product.c new file mode 100644 index 0000000..4c04c94 --- /dev/null +++ b/example_code/item_096/my_extension/dot_product.c @@ -0,0 +1,56 @@ +/* + * Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "my_extension.h" + +PyObject *dot_product(PyObject *self, PyObject *args) +{ + PyObject *left, *right; + if (!PyArg_ParseTuple(args, "OO", &left, &right)) { + return NULL; + } + if (!PyList_Check(left) || !PyList_Check(right)) { + PyErr_SetString(PyExc_TypeError, "Both arguments must be lists"); + return NULL; + } + + Py_ssize_t left_length = PyList_Size(left); + Py_ssize_t right_length = PyList_Size(right); + if (left_length == -1 || right_length == -1) { + return NULL; + } + if (left_length != right_length) { + PyErr_SetString(PyExc_ValueError, "Lists must be the same length"); + return NULL; + } + + double result = 0; + + for (Py_ssize_t i = 0; i < left_length; i++) { + PyObject *left_item = PyList_GET_ITEM(left, i); + PyObject *right_item = PyList_GET_ITEM(right, i); + + double left_double = PyFloat_AsDouble(left_item); + double right_double = PyFloat_AsDouble(right_item); + if (PyErr_Occurred()) { + return NULL; + } + + result += left_double * right_double; + } + + return PyFloat_FromDouble(result); +} diff --git a/example_code/item_096/my_extension/init.c b/example_code/item_096/my_extension/init.c new file mode 100644 index 0000000..b69892e --- /dev/null +++ b/example_code/item_096/my_extension/init.c @@ -0,0 +1,46 @@ +/* + * Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "my_extension.h" + +static PyMethodDef my_extension_methods[] = { + { + "dot_product", + dot_product, + METH_VARARGS, + "Compute dot product", + }, + { + NULL, + NULL, + 0, + NULL, + }, +}; + +static struct PyModuleDef my_extension = { + PyModuleDef_HEAD_INIT, + "my_extension", + "My C-extension module", + -1, + my_extension_methods, +}; + +PyMODINIT_FUNC +PyInit_my_extension(void) +{ + return PyModule_Create(&my_extension); +} diff --git a/example_code/item_096/my_extension/my_extension.h b/example_code/item_096/my_extension/my_extension.h new file mode 100644 index 0000000..799bcb0 --- /dev/null +++ b/example_code/item_096/my_extension/my_extension.h @@ -0,0 +1,20 @@ +/* + * Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define PY_SSIZE_T_CLEAN +#include + +PyObject *dot_product(PyObject *self, PyObject *args); diff --git a/example_code/item_096/my_extension2/dot_product.c b/example_code/item_096/my_extension2/dot_product.c new file mode 100644 index 0000000..58f8a6d --- /dev/null +++ b/example_code/item_096/my_extension2/dot_product.c @@ -0,0 +1,78 @@ +/* + * Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "my_extension2.h" + +PyObject *dot_product(PyObject *self, PyObject *args) +{ + PyObject *left, *right; + if (!PyArg_ParseTuple(args, "OO", &left, &right)) { + return NULL; + } + PyObject *left_iter = PyObject_GetIter(left); + if (left_iter == NULL) { + return NULL; + } + PyObject *right_iter = PyObject_GetIter(right); + if (right_iter == NULL) { + Py_DECREF(left_iter); + return NULL; + } + + PyObject *left_item = NULL; + PyObject *right_item = NULL; + PyObject *multiplied = NULL; + PyObject *result = PyLong_FromLong(0); + + while (1) { + Py_CLEAR(left_item); + Py_CLEAR(right_item); + Py_CLEAR(multiplied); + left_item = PyIter_Next(left_iter); + right_item = PyIter_Next(right_iter); + + if (left_item == NULL && right_item == NULL) { + break; + } else if (left_item == NULL || right_item == NULL) { + PyErr_SetString(PyExc_ValueError, "Arguments had unequal length"); + break; + } + + multiplied = PyNumber_Multiply(left_item, right_item); + if (multiplied == NULL) { + break; + } + PyObject *added = PyNumber_Add(result, multiplied); + if (added == NULL) { + break; + } + Py_CLEAR(result); + result = added; + } + + Py_CLEAR(left_item); + Py_CLEAR(right_item); + Py_CLEAR(multiplied); + Py_DECREF(left_iter); + Py_DECREF(right_iter); + + if (PyErr_Occurred()) { + Py_CLEAR(result); + return NULL; + } + + return result; +} diff --git a/example_code/item_096/my_extension2/init.c b/example_code/item_096/my_extension2/init.c new file mode 100644 index 0000000..195f405 --- /dev/null +++ b/example_code/item_096/my_extension2/init.c @@ -0,0 +1,46 @@ +/* + * Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "my_extension2.h" + +static PyMethodDef my_extension2_methods[] = { + { + "dot_product", + dot_product, + METH_VARARGS, + "Compute dot product", + }, + { + NULL, + NULL, + 0, + NULL, + }, +}; + +static struct PyModuleDef my_extension2 = { + PyModuleDef_HEAD_INIT, + "my_extension2", + "My second C-extension module", + -1, + my_extension2_methods, +}; + +PyMODINIT_FUNC +PyInit_my_extension2(void) +{ + return PyModule_Create(&my_extension2); +} diff --git a/example_code/item_096/my_extension2/my_extension2.h b/example_code/item_096/my_extension2/my_extension2.h new file mode 100644 index 0000000..799bcb0 --- /dev/null +++ b/example_code/item_096/my_extension2/my_extension2.h @@ -0,0 +1,20 @@ +/* + * Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define PY_SSIZE_T_CLEAN +#include + +PyObject *dot_product(PyObject *self, PyObject *args); diff --git a/example_code/item_096/my_extension2/setup.py b/example_code/item_096/my_extension2/setup.py new file mode 100644 index 0000000..529b231 --- /dev/null +++ b/example_code/item_096/my_extension2/setup.py @@ -0,0 +1,27 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from setuptools import Extension, setup + +setup( + name="my_extension2", + ext_modules=[ + Extension( + name="my_extension2", + sources=["init.c", "dot_product.c"], + ), + ], +) From fa87298a0ba042fc07fabadb599e5c3f6becf2d7 Mon Sep 17 00:00:00 2001 From: Brett Slatkin Date: Tue, 10 Dec 2024 15:45:02 -0800 Subject: [PATCH 12/12] Addressing openssl commandline warnings --- example_code/item_067.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example_code/item_067.py b/example_code/item_067.py index d1e634b..73b94d4 100755 --- a/example_code/item_067.py +++ b/example_code/item_067.py @@ -111,7 +111,7 @@ def run_encrypt(data): env = os.environ.copy() env["password"] = "zf7ShyBhZOraQDdE/FiZpm/m/8f9X+M1" proc = subprocess.Popen( - ["openssl", "enc", "-des3", "-pass", "env:password"], + ["openssl", "enc", "-des3", "-pbkdf2", "-pass", "env:password"], env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, @@ -138,7 +138,7 @@ def run_encrypt(data): print("Example 8") def run_hash(input_stdin): return subprocess.Popen( - ["openssl", "dgst", "-whirlpool", "-binary"], + ["openssl", "dgst", "-sha256", "-binary"], stdin=input_stdin, stdout=subprocess.PIPE, )