From ba8ffbbd6754b97d1ff696b3268da906d5138b66 Mon Sep 17 00:00:00 2001 From: Brett Slatkin Date: Tue, 10 Dec 2024 14:51:51 -0800 Subject: [PATCH 1/6] 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 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'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 2/6] 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 3/6] 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 4/6] 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 5/6] 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 6/6] 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, )