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/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 1495e76..b62bd02 100644 Binary files a/cover.jpg and b/cover.jpg differ diff --git a/example_code/item_01.py b/example_code/item_001.py similarity index 86% rename from example_code/item_01.py rename to example_code/item_001.py index 585a9c5..d6a34cf 100755 --- a/example_code/item_01.py +++ b/example_code/item_001.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,13 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +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_05.py b/example_code/item_004.py similarity index 55% rename from example_code/item_05.py rename to example_code/item_004.py index ec407db..2ff06ec 100755 --- a/example_code/item_05.py +++ b/example_code/item_004.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,70 +44,70 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") from urllib.parse import parse_qs -my_values = parse_qs('red=5&blue=0&green=', - keep_blank_values=True) +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')) +print("Example 2") +print("Red: ", my_values.get("red")) +print("Green: ", my_values.get("green")) +print("Opacity:", my_values.get("opacity")) -# Example 3 +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}') +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}') +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}") -# Example 5 -red_str = my_values.get('red', ['']) +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_str = my_values.get("green", [""]) green = int(green_str[0]) if green_str[0] else 0 -opacity_str = my_values.get('opacity', ['']) +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(f"Red: {red!r}") +print(f"Green: {green!r}") +print(f"Opacity: {opacity!r}") -# Example 6 -green_str = my_values.get('green', ['']) +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(f"Green: {green!r}") -# Example 7 +print("Example 7") def get_first_int(values, key, default=0): - found = values.get(key, ['']) + 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}') +print("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_005.py similarity index 50% rename from example_code/item_06.py rename to example_code/item_005.py index beafb39..f0964ba 100755 --- a/example_code/item_06.py +++ b/example_code/item_005.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,92 +44,98 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") +no_snack = () +snack = ("chips",) + + +print("Example 2") snack_calories = { - 'chips': 140, - 'popcorn': 80, - 'nuts': 190, + "chips": 140, + "popcorn": 80, + "nuts": 190, } -items = tuple(snack_calories.items()) +items = list(snack_calories.items()) print(items) -# Example 2 -item = ('Peanut butter', 'Jelly') -first = item[0] -second = item[1] -print(first, 'and', second) +print("Example 3") +item = ("Peanut butter", "Jelly") +first_item = item[0] # Index +first_half = item[:1] # Slice +print(first_item) +print(first_half) -# Example 3 +print("Example 4") try: - pair = ('Chocolate', 'Peanut butter') - pair[0] = 'Honey' + pair = ("Chocolate", "Peanut butter") + pair[0] = "Honey" except: logging.exception('Expected') else: assert False -# Example 4 -item = ('Peanut butter', 'Jelly') +print("Example 5") +item = ("Peanut butter", "Jelly") first, second = item # Unpacking -print(first, 'and', second) +print(first, "and", second) -# Example 5 +print("Example 6") favorite_snacks = { - 'salty': ('pretzels', 100), - 'sweet': ('cookies', 180), - 'veggie': ('carrots', 20), + "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(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 +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'] + 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 +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 + 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'] +names = ["pretzels", "carrots", "arugula", "bacon"] bubble_sort(names) print(names) -# Example 8 -snacks = [('bacon', 350), ('donut', 240), ('muffin', 190)] +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') + item = snacks[i] + name = item[0] + calories = item[1] + print(f"#{i+1}: {name} has {calories} calories") -# Example 9 +print("Example 10") for rank, (name, calories) in enumerate(snacks, 1): - print(f'#{rank}: {name} has {calories} calories') + 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_10.py b/example_code/item_008.py similarity index 66% rename from example_code/item_10.py rename to example_code/item_008.py index a106c38..b4ac6da 100755 --- a/example_code/item_10.py +++ b/example_code/item_008.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,68 +44,69 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") fresh_fruit = { - 'apple': 10, - 'banana': 8, - 'lemon': 5, + "apple": 10, + "banana": 8, + "lemon": 5, } -# Example 2 +print("Example 2") def make_lemonade(count): - print(f'Making {count} lemons into lemonade') + print(f"Making {count} lemons into lemonade") def out_of_stock(): - print('Out of stock!') + print("Out of stock!") -count = fresh_fruit.get('lemon', 0) +count = fresh_fruit.get("lemon", 0) if count: make_lemonade(count) else: out_of_stock() -# Example 3 -if count := fresh_fruit.get('lemon', 0): +print("Example 3") +if count := fresh_fruit.get("lemon", 0): make_lemonade(count) else: out_of_stock() -# Example 4 +print("Example 4") def make_cider(count): - print(f'Making cider with {count} apples') + print(f"Making cider with {count} apples") -count = fresh_fruit.get('apple', 0) +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: +print("Example 5") +if (count := fresh_fruit.get("apple", 0)) >= 4: make_cider(count) else: out_of_stock() -# Example 6 +print("Example 6") def slice_bananas(count): - print(f'Slicing {count} bananas') + 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') + print(f"Making smoothies with {count} banana slices") pieces = 0 -count = fresh_fruit.get('banana', 0) +count = fresh_fruit.get("banana", 0) if count >= 2: pieces = slice_bananas(count) @@ -115,12 +116,12 @@ def make_smoothies(count): out_of_stock() -# Example 7 -count = fresh_fruit.get('banana', 0) +print("Example 7") +count = fresh_fruit.get("banana", 0) if count >= 2: pieces = slice_bananas(count) else: - pieces = 0 + pieces = 0 # Moved try: smoothies = make_smoothies(pieces) @@ -128,9 +129,9 @@ def make_smoothies(count): out_of_stock() -# Example 8 +print("Example 8") pieces = 0 -if (count := fresh_fruit.get('banana', 0)) >= 2: +if (count := fresh_fruit.get("banana", 0)) >= 2: # Changed pieces = slice_bananas(count) try: @@ -139,11 +140,11 @@ def make_smoothies(count): out_of_stock() -# Example 9 -if (count := fresh_fruit.get('banana', 0)) >= 2: +print("Example 9") +if (count := fresh_fruit.get("banana", 0)) >= 2: pieces = slice_bananas(count) else: - pieces = 0 + pieces = 0 # Moved try: smoothies = make_smoothies(pieces) @@ -151,40 +152,40 @@ def make_smoothies(count): out_of_stock() -# Example 10 -count = fresh_fruit.get('banana', 0) +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) + count = fresh_fruit.get("apple", 0) if count >= 4: to_enjoy = make_cider(count) else: - count = fresh_fruit.get('lemon', 0) + count = fresh_fruit.get("lemon", 0) if count: to_enjoy = make_lemonade(count) else: - to_enjoy = 'Nothing' + to_enjoy = "Nothing" -# Example 11 -if (count := fresh_fruit.get('banana', 0)) >= 2: +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: +elif (count := fresh_fruit.get("apple", 0)) >= 4: to_enjoy = make_cider(count) -elif count := fresh_fruit.get('lemon', 0): +elif count := fresh_fruit.get("lemon", 0): to_enjoy = make_lemonade(count) else: - to_enjoy = 'Nothing' + to_enjoy = "Nothing" -# Example 12 +print("Example 12") FRUIT_TO_PICK = [ - {'apple': 1, 'banana': 3}, - {'lemon': 2, 'lime': 5}, - {'orange': 3, 'melon': 2}, + {"apple": 1, "banana": 3}, + {"lemon": 2, "lime": 5}, + {"orange": 3, "melon": 2}, ] def pick_fruit(): @@ -207,13 +208,12 @@ def make_juice(fruit, count): print(bottles) -# Example 13 +print("Example 13") FRUIT_TO_PICK = [ - {'apple': 1, 'banana': 3}, - {'lemon': 2, 'lime': 5}, - {'orange': 3, 'melon': 2}, + {"apple": 1, "banana": 3}, + {"lemon": 2, "lime": 5}, + {"orange": 3, "melon": 2}, ] - bottles = [] while True: # Loop fresh_fruit = pick_fruit() @@ -226,15 +226,15 @@ def make_juice(fruit, count): print(bottles) -# Example 14 +print("Example 14") FRUIT_TO_PICK = [ - {'apple': 1, 'banana': 3}, - {'lemon': 2, 'lime': 5}, - {'orange': 3, 'melon': 2}, + {"apple": 1, "banana": 3}, + {"lemon": 2, "lime": 5}, + {"orange": 3, "melon": 2}, ] bottles = [] -while fresh_fruit := pick_fruit(): +while fresh_fruit := pick_fruit(): # Changed for fruit, count in fresh_fruit.items(): batch = make_juice(fruit, count) bottles.extend(batch) 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_03.py b/example_code/item_010.py similarity index 61% rename from example_code/item_03.py rename to example_code/item_010.py index 24eb0a7..0974798 100755 --- a/example_code/item_03.py +++ b/example_code/item_010.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,137 +44,144 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 -a = b'h\x65llo' +print("Example 1") +a = b"h\x65llo" +print(type(a)) print(list(a)) print(a) -# Example 2 -a = 'a\u0300 propos' +print("Example 2") +a = "a\u0300 propos" +print(type(a)) print(list(a)) print(a) -# Example 3 +print("Example 3") def to_str(bytes_or_str): if isinstance(bytes_or_str, bytes): - value = bytes_or_str.decode('utf-8') + 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(repr(to_str(b"foo"))) +print(repr(to_str("bar"))) -# Example 4 +print("Example 4") def to_bytes(bytes_or_str): if isinstance(bytes_or_str, str): - value = bytes_or_str.encode('utf-8') + 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(repr(to_bytes(b"foo"))) +print(repr(to_bytes("bar"))) -# Example 5 -print(b'one' + b'two') -print('one' + 'two') +print("Example 5") +print(b"one" + b"two") +print("one" + "two") -# Example 6 +print("Example 6") try: - b'one' + 'two' + b"one" + "two" except: logging.exception('Expected') else: assert False -# Example 7 +print("Example 7") try: - 'one' + b'two' + "one" + b"two" except: logging.exception('Expected') else: assert False -# Example 8 -assert b'red' > b'blue' -assert 'red' > 'blue' +print("Example 8") +assert b"red" > b"blue" +assert "red" > "blue" -# Example 9 +print("Example 9") try: - assert 'red' > b'blue' + assert "red" > b"blue" except: logging.exception('Expected') else: assert False -# Example 10 +print("Example 10") try: - assert b'blue' < 'red' + assert b"blue" < "red" except: logging.exception('Expected') else: assert False -# Example 11 -print(b'foo' == 'foo') +print("Example 11") +print(b"foo" == "foo") -# Example 12 -print(b'red %s' % b'blue') -print('red %s' % 'blue') +print("Example 12") +blue_bytes = b"blue" +blue_str = "blue" +print(b"red %s" % blue_bytes) +print("red %s" % blue_str) -# Example 13 +print("Example 13") try: - print(b'red %s' % 'blue') + print(b"red %s" % blue_str) except: logging.exception('Expected') else: assert False -# Example 14 -print('red %s' % b'blue') +print("Example 14") +print("red %s" % blue_bytes) +print(f"red {blue_bytes}") -# Example 15 +print("Example 15") try: - with open('data.bin', 'w') as f: - f.write(b'\xf1\xf2\xf3\xf4\xf5') + 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') +print("Example 16") +with open("data.bin", "wb") as f: + f.write(b"\xf1\xf2\xf3\xf4\xf5") -# Example 17 +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' + kwargs["encoding"] = "utf-8" return real_open(*args, **kwargs) - with open('data.bin', 'r') as f: + with open("data.bin", "r") as f: data = f.read() except: logging.exception('Expected') @@ -182,18 +189,15 @@ def open(*args, **kwargs): assert False -# Example 18 +print("Example 18") # Restore the overloaded open above. open = real_open - -with open('data.bin', 'rb') as f: +with open("data.bin", "rb") as f: data = f.read() +assert data == b"\xf1\xf2\xf3\xf4\xf5" -assert data == b'\xf1\xf2\xf3\xf4\xf5' - -# Example 19 -with open('data.bin', 'r', encoding='cp1252') as f: +print("Example 19") +with open("data.bin", "r", encoding="cp1252") as f: data = f.read() - -assert data == 'ñòóôõ' +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_75.py b/example_code/item_012.py similarity index 59% rename from example_code/item_75.py rename to example_code/item_012.py index 5e43409..3a43859 100755 --- a/example_code/item_75.py +++ b/example_code/item_012.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,80 +44,87 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 -print('foo bar') +print("Example 1") +print("foo bar") -# Example 2 -my_value = 'foo bar' +print("Example 2") +my_value = "foo bar" print(str(my_value)) -print('%s' % my_value) -print(f'{my_value}') +print("%s" % my_value) +print(f"{my_value}") print(format(my_value)) -print(my_value.__format__('s')) +print(my_value.__format__("s")) print(my_value.__str__()) -# Example 3 -print(5) -print('5') - +print("Example 3") int_value = 5 -str_value = '5' -print(f'{int_value} == {str_value} ?') +str_value = "5" +print(int_value) +print(str_value) +print(f"Is {int_value} == {str_value}?") -# Example 4 -a = '\x07' +print("Example 4") +a = "\x07" print(repr(a)) -# Example 5 +print("Example 5") b = eval(repr(a)) assert a == b -# Example 6 -print(repr(5)) -print(repr('5')) - +print("Example 6") +print(repr(int_value)) +print(repr(str_value)) -# Example 7 -print('%r' % 5) -print('%r' % '5') -int_value = 5 -str_value = '5' -print(f'{int_value!r} != {str_value!r}') +print("Example 7") +print("Is %r == %r?" % (int_value, str_value)) +print(f"Is {int_value!r} == {str_value!r}?") -# Example 8 +print("Example 8") class OpaqueClass: def __init__(self, x, y): self.x = x self.y = y -obj = OpaqueClass(1, 'foo') +obj = OpaqueClass(1, "foo") print(obj) -# Example 9 +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})' + return f"BetterClass({self.x!r}, {self.y!r})" -# Example 10 -obj = BetterClass(2, 'bar') +print("Example 10") +obj = BetterClass(2, "bar") print(obj) -# Example 11 -obj = OpaqueClass(4, 'baz') -print(obj.__dict__) +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_11.py b/example_code/item_014.py similarity index 59% rename from example_code/item_11.py rename to example_code/item_014.py index f502f34..2902a15 100755 --- a/example_code/item_11.py +++ b/example_code/item_014.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,23 +44,24 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# 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 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 +print("Example 2") assert a[:5] == a[0:5] -# Example 3 +print("Example 3") assert a[5:] == a[5:len(a)] -# Example 4 +print("Example 4") print(a[:]) print(a[:5]) print(a[:-1]) @@ -71,23 +72,23 @@ def close_open_files(): 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'] +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"] -# Example 6 +print("Example 6") first_twenty_items = a[:20] last_twenty_items = a[-20:] -# Example 7 +print("Example 7") try: a[20] except: @@ -96,36 +97,36 @@ def close_open_files(): assert False -# Example 8 +print("Example 8") b = a[3:] -print('Before: ', b) +print("Before: ", b) b[1] = 99 -print('After: ', b) -print('No change:', a) +print("After: ", b) +print("No change:", a) -# Example 9 -print('Before ', a) +print("Example 9") +print("Before ", a) a[2:7] = [99, 22, 14] -print('After ', a) +print("After ", a) -# Example 10 -print('Before ', a) +print("Example 10") +print("Before ", a) a[2:3] = [47, 11] -print('After ', a) +print("After ", a) -# Example 11 +print("Example 11") b = a[:] assert b == a and b is not a -# Example 12 +print("Example 12") b = a -print('Before a', a) -print('Before b', b) +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 +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_13.py b/example_code/item_016.py similarity index 73% rename from example_code/item_13.py rename to example_code/item_016.py index d2b9349..bc995ce 100755 --- a/example_code/item_13.py +++ b/example_code/item_016.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") try: car_ages = [0, 9, 4, 8, 7, 20, 19, 1, 6, 15] car_ages_descending = sorted(car_ages, reverse=True) @@ -57,19 +58,19 @@ def close_open_files(): assert False -# Example 2 +print("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 +print("Example 3") oldest, second_oldest, *others = car_ages_descending print(oldest, second_oldest, others) -# Example 4 +print("Example 4") oldest, *others, youngest = car_ages_descending print(oldest, youngest, others) @@ -77,7 +78,7 @@ def close_open_files(): print(youngest, second_youngest, others) -# Example 5 +print("Example 5") try: # This will not compile source = """*others = car_ages_descending""" @@ -88,7 +89,7 @@ def close_open_files(): assert False -# Example 6 +print("Example 6") try: # This will not compile source = """first, *middle, *second_middle, last = [1, 2, 3, 4]""" @@ -99,49 +100,47 @@ def close_open_files(): assert False -# Example 7 +print("Example 7") car_inventory = { - 'Downtown': ('Silver Shadow', 'Pinto', 'DMC'), - 'Airport': ('Skyline', 'Viper', 'Gremlin', 'Nova'), + "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(f"Best at {loc1} is {best1}, {len(rest1)} others") +print(f"Best at {loc2} is {best2}, {len(rest2)} others") -# Example 8 +print("Example 8") short_list = [1, 2] first, second, *rest = short_list print(first, second, rest) -# Example 9 +print("Example 9") it = iter(range(1, 3)) first, second = it -print(f'{first} and {second}') +print(f"{first} and {second}") -# Example 10 +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') + 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 +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("CSV Header:", header) +print("Row count: ", len(rows)) -# Example 12 +print("Example 12") it = generate_csv() header, *rows = it -print('CSV Header:', header) -print('Row count: ', len(rows)) +print("CSV Header:", header) +print("Row count: ", len(rows)) diff --git a/example_code/item_07.py b/example_code/item_017.py similarity index 78% rename from example_code/item_07.py rename to example_code/item_017.py index c1f4239..700610b 100755 --- a/example_code/item_07.py +++ b/example_code/item_017.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") from random import randint random_bits = 0 @@ -57,29 +58,29 @@ def close_open_files(): print(bin(random_bits)) -# Example 2 -flavor_list = ['vanilla', 'chocolate', 'pecan', 'strawberry'] +print("Example 2") +flavor_list = ["vanilla", "chocolate", "pecan", "strawberry"] for flavor in flavor_list: - print(f'{flavor} is delicious') + print(f"{flavor} is delicious") -# Example 3 +print("Example 3") for i in range(len(flavor_list)): flavor = flavor_list[i] - print(f'{i + 1}: {flavor}') + print(f"{i + 1}: {flavor}") -# Example 4 +print("Example 4") it = enumerate(flavor_list) print(next(it)) print(next(it)) -# Example 5 +print("Example 5") for i, flavor in enumerate(flavor_list): - print(f'{i + 1}: {flavor}') + print(f"{i + 1}: {flavor}") -# Example 6 +print("Example 6") for i, flavor in enumerate(flavor_list, 1): - print(f'{i}: {flavor}') + print(f"{i}: {flavor}") diff --git a/example_code/item_08.py b/example_code/item_018.py similarity index 72% rename from example_code/item_08.py rename to example_code/item_018.py index c22151c..dbcec05 100755 --- a/example_code/item_08.py +++ b/example_code/item_018.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,15 +44,16 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 -names = ['Cecilia', 'Lise', 'Marie'] +print("Example 1") +names = ["Cecilia", "Lise", "Marie"] counts = [len(n) for n in names] print(counts) -# Example 2 +print("Example 2") longest_name = None max_count = 0 @@ -65,35 +66,40 @@ def close_open_files(): print(longest_name) -# Example 3 +print("Example 3") longest_name = None max_count = 0 -for i, name in enumerate(names): + +for i, name in enumerate(names): # Changed count = counts[i] if count > max_count: - longest_name = name + longest_name = name # Changed max_count = count -assert longest_name == 'Cecilia' +assert longest_name == "Cecilia" -# Example 4 +print("Example 4") longest_name = None max_count = 0 -for name, count in zip(names, counts): + +for name, count in zip(names, counts): # Changed if count > max_count: longest_name = name max_count = count -assert longest_name == 'Cecilia' +assert longest_name == "Cecilia" -# Example 5 -names.append('Rosalind') +print("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}') +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_09.py b/example_code/item_019.py similarity index 77% rename from example_code/item_09.py rename to example_code/item_019.py index d96d033..64b6295 100755 --- a/example_code/item_09.py +++ b/example_code/item_019.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,52 +44,53 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") for i in range(3): - print('Loop', i) + print("Loop", i) else: - print('Else block!') + print("Else block!") -# Example 2 +print("Example 2") for i in range(3): - print('Loop', i) + print("Loop", i) if i == 1: break else: - print('Else block!') + print("Else block!") -# Example 3 +print("Example 3") for x in []: - print('Never runs') + print("Never runs") else: - print('For Else block!') + print("For else block!") -# Example 4 +print("Example 4") while False: - print('Never runs') + print("Never runs") else: - print('While Else block!') + print("While else block!") -# Example 5 +print("Example 5") a = 4 b = 9 for i in range(2, min(a, b) + 1): - print('Testing', i) + print("Testing", i) if a % i == 0 and b % i == 0: - print('Not coprime') + print("Not coprime") break else: - print('Coprime') + print("Coprime") -# Example 6 +print("Example 6") def coprime(a, b): for i in range(2, min(a, b) + 1): if a % i == 0 and b % i == 0: @@ -100,7 +101,7 @@ def coprime(a, b): assert not coprime(3, 6) -# Example 7 +print("Example 7") def coprime_alternate(a, b): is_coprime = True for i in range(2, min(a, b) + 1): 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_31.py b/example_code/item_021.py similarity index 78% rename from example_code/item_31.py rename to example_code/item_021.py index 122d480..c871012 100755 --- a/example_code/item_31.py +++ b/example_code/item_021.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") def normalize(numbers): total = sum(numbers) result = [] @@ -56,18 +57,18 @@ def normalize(numbers): return result -# Example 2 +print("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: +print("Example 3") +path = "my_numbers.txt" +with open(path, "w") as f: for i in (15, 35, 80): - f.write('%d\n' % i) + f.write(f"{i}\n") def read_visits(data_path): with open(data_path) as f: @@ -75,19 +76,19 @@ def read_visits(data_path): yield int(line) -# Example 4 -it = read_visits('my_numbers.txt') +print("Example 4") +it = read_visits("my_numbers.txt") percentages = normalize(it) print(percentages) -# Example 5 -it = read_visits('my_numbers.txt') +print("Example 5") +it = read_visits("my_numbers.txt") print(list(it)) print(list(it)) # Already exhausted -# Example 6 +print("Example 6") def normalize_copy(numbers): numbers_copy = list(numbers) # Copy the iterator total = sum(numbers_copy) @@ -98,14 +99,14 @@ def normalize_copy(numbers): return result -# Example 7 -it = read_visits('my_numbers.txt') +print("Example 7") +it = read_visits("my_numbers.txt") percentages = normalize_copy(it) print(percentages) assert sum(percentages) == 100.0 -# Example 8 +print("Example 8") def normalize_func(get_iter): total = sum(get_iter()) # New iterator result = [] @@ -115,14 +116,14 @@ def normalize_func(get_iter): return result -# Example 9 -path = 'my_numbers.txt' +print("Example 9") +path = "my_numbers.txt" percentages = normalize_func(lambda: read_visits(path)) print(percentages) assert sum(percentages) == 100.0 -# Example 10 +print("Example 10") class ReadVisits: def __init__(self, data_path): self.data_path = data_path @@ -133,17 +134,17 @@ def __iter__(self): yield int(line) -# Example 11 +print("Example 11") visits = ReadVisits(path) -percentages = normalize(visits) +percentages = normalize(visits) # Changed print(percentages) assert sum(percentages) == 100.0 -# Example 12 +print("Example 12") def normalize_defensive(numbers): if iter(numbers) is numbers: # An iterator -- bad! - raise TypeError('Must supply a container') + raise TypeError("Must supply a container") total = sum(numbers) result = [] for value in numbers: @@ -151,6 +152,7 @@ def normalize_defensive(numbers): result.append(percent) return result + visits = [15, 35, 80] normalize_defensive(visits) # No error @@ -163,12 +165,12 @@ def normalize_defensive(numbers): assert False -# Example 13 -from collections.abc import Iterator +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') + raise TypeError("Must supply a container") total = sum(numbers) result = [] for value in numbers: @@ -176,6 +178,7 @@ def normalize_defensive(numbers): result.append(percent) return result + visits = [15, 35, 80] normalize_defensive(visits) # No error @@ -188,17 +191,18 @@ def normalize_defensive(numbers): assert False -# Example 14 -visits = [15, 35, 80] -percentages = normalize_defensive(visits) -assert sum(percentages) == 100.0 +print("Example 14") +visits_list = [15, 35, 80] +list_percentages = normalize_defensive(visits_list) -visits = ReadVisits(path) -percentages = normalize_defensive(visits) +visits_obj = ReadVisits(path) +obj_percentages = normalize_defensive(visits_obj) + +assert list_percentages == obj_percentages assert sum(percentages) == 100.0 -# Example 15 +print("Example 15") try: visits = [15, 35, 80] it = iter(visits) 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_36.py b/example_code/item_024.py similarity index 63% rename from example_code/item_36.py rename to example_code/item_024.py index 5829639..bafaab7 100755 --- a/example_code/item_36.py +++ b/example_code/item_024.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,104 +44,129 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") import itertools -# Example 2 +print("Example 2") it = itertools.chain([1, 2, 3], [4, 5, 6]) print(list(it)) -# Example 3 -it = itertools.repeat('hello', 3) +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)) -# Example 4 +print("Example 5") it = itertools.cycle([1, 2]) -result = [next(it) for _ in range (10)] +result = [next(it) for _ in range(10)] print(result) -# Example 5 -it1, it2, it3 = itertools.tee(['first', 'second'], 3) +print("Example 6") +it1, it2, it3 = itertools.tee(["first", "second"], 3) print(list(it1)) print(list(it2)) print(list(it3)) -# Example 6 -keys = ['one', 'two', 'three'] +print("Example 7") +keys = ["one", "two", "three"] values = [1, 2] normal = list(zip(keys, values)) -print('zip: ', normal) +print("zip: ", normal) -it = itertools.zip_longest(keys, values, fillvalue='nope') +it = itertools.zip_longest(keys, values, fillvalue="nope") longest = list(it) -print('zip_longest:', longest) +print("zip_longest:", longest) -# Example 7 +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)) +print("First five: ", list(first_five)) middle_odds = itertools.islice(values, 2, 8, 2) -print('Middle odds:', list(middle_odds)) +print("Middle odds:", list(middle_odds)) -# Example 8 +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)) -# Example 9 +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)) -# Example 10 +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)) +print("Filter: ", list(filter_result)) filter_false_result = itertools.filterfalse(evens, values) -print('Filter false:', list(filter_false_result)) +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)) -# Example 11 +print("Example 15") values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] sum_reduce = itertools.accumulate(values) -print('Sum: ', list(sum_reduce)) +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("Modulo:", list(modulo_reduce)) -# Example 12 +print("Example 16") single = itertools.product([1, 2], repeat=2) -print('Single: ', list(single)) +print("Single: ", list(single)) -multiple = itertools.product([1, 2], ['a', 'b']) -print('Multiple:', list(multiple)) +multiple = itertools.product([1, 2], ["a", "b"]) +print("Multiple:", list(multiple)) -# Example 13 +print("Example 17") it = itertools.permutations([1, 2, 3, 4], 2) original_print = print print = pprint @@ -149,12 +174,12 @@ def sum_modulo_20(first, second): print = original_print -# Example 14 +print("Example 18") it = itertools.combinations([1, 2, 3, 4], 2) print(list(it)) -# Example 15 +print("Example 19") it = itertools.combinations_with_replacement([1, 2, 3, 4], 2) original_print = print print = pprint diff --git a/example_code/item_15.py b/example_code/item_025.py similarity index 75% rename from example_code/item_15.py rename to example_code/item_025.py index ef1ab90..9462683 100755 --- a/example_code/item_15.py +++ b/example_code/item_025.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,51 +44,52 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 2 +print("Example 2") baby_names = { - 'cat': 'kitten', - 'dog': 'puppy', + "cat": "kitten", + "dog": "puppy", } print(baby_names) -# Example 4 +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 -# Example 6 +print("Example 6") def my_func(**kwargs): for key, value in kwargs.items(): - print(f'{key} = {value}') + print(f"{key} = {value}") -my_func(goose='gosling', kangaroo='joey') +my_func(goose="gosling", kangaroo="joey") -# Example 8 +print("Example 8") class MyClass: def __init__(self): - self.alligator = 'hatchling' - self.elephant = 'calf' + self.alligator = "hatchling" + self.elephant = "calf" a = MyClass() for key, value in a.__dict__.items(): - print(f'{key} = {value}') + print(f"{key} = {value}") -# Example 9 +print("Example 9") votes = { - 'otter': 1281, - 'polar bear': 587, - 'fox': 863, + "otter": 1281, + "polar bear": 587, + "fox": 863, } -# Example 10 +print("Example 10") def populate_ranks(votes, ranks): names = list(votes.keys()) names.sort(key=votes.get, reverse=True) @@ -96,12 +97,12 @@ def populate_ranks(votes, ranks): ranks[name] = i -# Example 11 +print("Example 11") def get_winner(ranks): return next(iter(ranks)) -# Example 12 +print("Example 12") ranks = {} populate_ranks(votes, ranks) print(ranks) @@ -109,7 +110,7 @@ def get_winner(ranks): print(winner) -# Example 13 +print("Example 13") from collections.abc import MutableMapping class SortedDict(MutableMapping): @@ -134,25 +135,26 @@ def __iter__(self): 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 +my_dict["otter"] = 1 +my_dict["cheeta"] = 2 +my_dict["anteater"] = 3 +my_dict["deer"] = 4 -assert my_dict['otter'] == 1 +assert my_dict["otter"] == 1 -assert 'cheeta' in my_dict -del my_dict['cheeta'] -assert 'cheeta' not in my_dict +assert "cheeta" in my_dict +del my_dict["cheeta"] +assert "cheeta" not in my_dict -expected = [('anteater', 3), ('deer', 4), ('otter', 1)] +expected = [("anteater", 3), ("deer", 4), ("otter", 1)] assert list(my_dict.items()) == expected assert not isinstance(my_dict, dict) -# Example 14 +print("Example 14") sorted_ranks = SortedDict() populate_ranks(votes, sorted_ranks) print(sorted_ranks.data) @@ -160,7 +162,7 @@ def __len__(self): print(winner) -# Example 15 +print("Example 15") def get_winner(ranks): for name, rank in ranks.items(): if rank == 1: @@ -170,14 +172,15 @@ def get_winner(ranks): print(winner) -# Example 16 +print("Example 16") try: def get_winner(ranks): if not isinstance(ranks, dict): - raise TypeError('must provide a dict instance') + raise TypeError("must provide a dict instance") return next(iter(ranks)) - assert get_winner(ranks) == 'otter' + + assert get_winner(ranks) == "otter" get_winner(sorted_ranks) except: diff --git a/example_code/item_15_example_01.py b/example_code/item_025_example_01.py similarity index 84% rename from example_code/item_15_example_01.py rename to example_code/item_025_example_01.py index a797385..8c83fcb 100755 --- a/example_code/item_15_example_01.py +++ b/example_code/item_025_example_01.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3.5 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,10 +16,10 @@ -# Example 1 +print("Example 1") # Python 3.5 baby_names = { - 'cat': 'kitten', - 'dog': 'puppy', + "cat": "kitten", + "dog": "puppy", } print(baby_names) diff --git a/example_code/item_15_example_03.py b/example_code/item_025_example_03.py similarity index 86% rename from example_code/item_15_example_03.py rename to example_code/item_025_example_03.py index 39c54b1..7b89df6 100755 --- a/example_code/item_15_example_03.py +++ b/example_code/item_025_example_03.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3.5 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,11 +16,11 @@ -# Example 3 +print("Example 3") # Python 3.5 baby_names = { - 'cat': 'kitten', - 'dog': 'puppy', + "cat": "kitten", + "dog": "puppy", } print(list(baby_names.keys())) print(list(baby_names.values())) diff --git a/example_code/item_15_example_05.py b/example_code/item_025_example_05.py similarity index 80% rename from example_code/item_15_example_05.py rename to example_code/item_025_example_05.py index 5280de0..ed7a41c 100755 --- a/example_code/item_15_example_05.py +++ b/example_code/item_025_example_05.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3.5 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,10 +16,10 @@ -# Example 5 +print("Example 5") # Python 3.5 def my_func(**kwargs): for key, value in kwargs.items(): - print('%s = %s' % (key, value)) + print("%s = %s" % (key, value)) -my_func(goose='gosling', kangaroo='joey') +my_func(goose="gosling", kangaroo="joey") diff --git a/example_code/item_15_example_07.py b/example_code/item_025_example_07.py similarity index 79% rename from example_code/item_15_example_07.py rename to example_code/item_025_example_07.py index 7073d5c..2b8467d 100755 --- a/example_code/item_15_example_07.py +++ b/example_code/item_025_example_07.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3.5 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,13 +16,13 @@ -# Example 7 +print("Example 7") # Python 3.5 class MyClass: def __init__(self): - self.alligator = 'hatchling' - self.elephant = 'calf' + self.alligator = "hatchling" + self.elephant = "calf" a = MyClass() for key, value in a.__dict__.items(): - print('%s = %s' % (key, value)) + print("%s = %s" % (key, value)) diff --git a/example_code/item_15_example_17.py b/example_code/item_025_example_17.py similarity index 82% rename from example_code/item_15_example_17.py rename to example_code/item_025_example_17.py index 83fa498..cb6f973 100755 --- a/example_code/item_15_example_17.py +++ b/example_code/item_025_example_17.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,15 +16,14 @@ -# Example 17 -# Check types in this file with: python -m mypy +print("Example 17") +# Check types in this file with: python3 -m mypy from typing import Dict, MutableMapping -def populate_ranks(votes: Dict[str, int], - ranks: Dict[str, int]) -> None: +def populate_ranks(votes: Dict[str, int], ranks: Dict[str, int]) -> None: names = list(votes.keys()) - names.sort(key=votes.get, reverse=True) + names.sort(key=votes.__getitem__, reverse=True) for i, name in enumerate(names, 1): ranks[name] = i @@ -55,10 +54,11 @@ def __iter__(self) -> Iterator[str]: def __len__(self) -> int: return len(self.data) + votes = { - 'otter': 1281, - 'polar bear': 587, - 'fox': 863, + "otter": 1281, + "polar bear": 587, + "fox": 863, } sorted_ranks = SortedDict() diff --git a/example_code/item_16.py b/example_code/item_026.py similarity index 74% rename from example_code/item_16.py rename to example_code/item_026.py index cf616ad..c6c58d8 100755 --- a/example_code/item_16.py +++ b/example_code/item_026.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,17 +44,18 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") counters = { - 'pumpernickel': 2, - 'sourdough': 1, + "pumpernickel": 2, + "sourdough": 1, } -# Example 2 -key = 'wheat' +print("Example 2") +key = "wheat" if key in counters: count = counters[key] @@ -62,12 +63,11 @@ def close_open_files(): count = 0 counters[key] = count + 1 - print(counters) -# Example 3 -key = 'brioche' +print("Example 3") +key = "brioche" try: count = counters[key] @@ -79,8 +79,8 @@ def close_open_files(): print(counters) -# Example 4 -key = 'multigrain' +print("Example 4") +key = "multigrain" count = counters.get(key, 0) counters[key] = count + 1 @@ -88,21 +88,21 @@ def close_open_files(): print(counters) -# Example 5 -key = 'baguette' +print("Example 5") +key = "baguette" if key not in counters: counters[key] = 0 counters[key] += 1 -key = 'ciabatta' +key = "ciabatta" if key in counters: counters[key] += 1 else: counters[key] = 1 -key = 'ciabatta' +key = "ciabatta" try: counters[key] += 1 @@ -112,14 +112,14 @@ def close_open_files(): print(counters) -# Example 6 +print("Example 6") votes = { - 'baguette': ['Bob', 'Alice'], - 'ciabatta': ['Coco', 'Deb'], + "baguette": ["Bob", "Alice"], + "ciabatta": ["Coco", "Deb"], } -key = 'brioche' -who = 'Elmer' +key = "brioche" +who = "Elmer" if key in votes: names = votes[key] @@ -130,9 +130,9 @@ def close_open_files(): print(votes) -# Example 7 -key = 'rye' -who = 'Felix' +print("Example 7") +key = "rye" +who = "Felix" try: names = votes[key] @@ -144,9 +144,9 @@ def close_open_files(): print(votes) -# Example 8 -key = 'wheat' -who = 'Gertrude' +print("Example 8") +key = "wheat" +who = "Gertrude" names = votes.get(key) if names is None: @@ -157,9 +157,9 @@ def close_open_files(): print(votes) -# Example 9 -key = 'brioche' -who = 'Hugh' +print("Example 9") +key = "brioche" +who = "Hugh" if (names := votes.get(key)) is None: votes[key] = names = [] @@ -169,9 +169,9 @@ def close_open_files(): print(votes) -# Example 10 -key = 'cornbread' -who = 'Kirk' +print("Example 10") +key = "cornbread" +who = "Kirk" names = votes.setdefault(key, []) names.append(who) @@ -179,18 +179,18 @@ def close_open_files(): print(votes) -# Example 11 +print("Example 11") data = {} -key = 'foo' +key = "foo" value = [] data.setdefault(key, value) -print('Before:', data) -value.append('hello') -print('After: ', data) +print("Before:", data) +value.append("hello") +print("After: ", data) -# Example 12 -key = 'dutch crunch' +print("Example 12") +key = "dutch crunch" count = counters.setdefault(key, 0) counters[key] = count + 1 diff --git a/example_code/item_17.py b/example_code/item_027.py similarity index 74% rename from example_code/item_17.py rename to example_code/item_027.py index b0e86ff..9c5732b 100755 --- a/example_code/item_17.py +++ b/example_code/item_027.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,29 +44,32 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") visits = { - 'Mexico': {'Tulum', 'Puerto Vallarta'}, - 'Japan': {'Hakone'}, + "Mexico": {"Tulum", "Puerto Vallarta"}, + "Japan": {"Hakone"}, } -# Example 2 -visits.setdefault('France', set()).add('Arles') # Short +print("Example 2") +# Short +visits.setdefault("France", set()).add("Arles") -if (japan := visits.get('Japan')) is None: # Long - visits['Japan'] = japan = set() -japan.add('Kyoto') +# 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 -# Example 3 +print("Example 3") class Visits: def __init__(self): self.data = {} @@ -76,14 +79,14 @@ def add(self, country, city): city_set.add(city) -# Example 4 +print("Example 4") visits = Visits() -visits.add('Russia', 'Yekaterinburg') -visits.add('Tanzania', 'Zanzibar') +visits.add("Russia", "Yekaterinburg") +visits.add("Tanzania", "Zanzibar") print(visits.data) -# Example 5 +print("Example 5") from collections import defaultdict class Visits: @@ -94,6 +97,6 @@ def add(self, country, city): self.data[country].add(city) visits = Visits() -visits.add('England', 'Bath') -visits.add('England', 'London') +visits.add("England", "Bath") +visits.add("England", "London") print(visits.data) diff --git a/example_code/item_18.py b/example_code/item_028.py similarity index 67% rename from example_code/item_18.py rename to example_code/item_028.py index 2d9814f..11903ed 100755 --- a/example_code/item_18.py +++ b/example_code/item_028.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,20 +44,21 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") pictures = {} -path = 'profile_1234.png' +path = "profile_1234.png" -with open(path, 'wb') as f: - f.write(b'image data here 1234') +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') + handle = open(path, "a+b") except OSError: - print(f'Failed to open path {path}') + print(f"Failed to open path {path}") raise else: pictures[path] = handle @@ -69,21 +70,21 @@ def close_open_files(): print(image_data) -# Example 2 +print("Example 2") # Examples using in and KeyError pictures = {} -path = 'profile_9991.png' +path = "profile_9991.png" -with open(path, 'wb') as f: - f.write(b'image data here 9991') +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') + handle = open(path, "a+b") except OSError: - print(f'Failed to open path {path}') + print(f"Failed to open path {path}") raise else: pictures[path] = handle @@ -95,18 +96,18 @@ def close_open_files(): print(image_data) pictures = {} -path = 'profile_9922.png' +path = "profile_9922.png" -with open(path, 'wb') as f: - f.write(b'image data here 9991') +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') + handle = open(path, "a+b") except OSError: - print(f'Failed to open path {path}') + print(f"Failed to open path {path}") raise else: pictures[path] = handle @@ -118,17 +119,17 @@ def close_open_files(): print(image_data) -# Example 3 +print("Example 3") pictures = {} -path = 'profile_9239.png' +path = "profile_9239.png" -with open(path, 'wb') as f: - f.write(b'image data here 9239') +with open(path, "wb") as f: + f.write(b"image data here 9239") try: - handle = pictures.setdefault(path, open(path, 'a+b')) + handle = pictures.setdefault(path, open(path, "a+b")) except OSError: - print(f'Failed to open path {path}') + print(f"Failed to open path {path}") raise else: handle.seek(0) @@ -138,20 +139,20 @@ def close_open_files(): print(image_data) -# Example 4 +print("Example 4") try: - path = 'profile_4555.csv' + path = "profile_4555.csv" - with open(path, 'wb') as f: - f.write(b'image data here 9239') + 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') + return open(profile_path, "a+b") except OSError: - print(f'Failed to open path {profile_path}') + print(f"Failed to open path {profile_path}") raise pictures = defaultdict(open_picture) @@ -164,17 +165,17 @@ def open_picture(profile_path): assert False -# Example 5 -path = 'account_9090.csv' +print("Example 5") +path = "account_9090.csv" -with open(path, 'wb') as f: - f.write(b'image data here 9090') +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') + return open(profile_path, "a+b") except OSError: - print(f'Failed to open path {profile_path}') + print(f"Failed to open path {profile_path}") raise class Pictures(dict): diff --git a/example_code/item_37.py b/example_code/item_029.py similarity index 72% rename from example_code/item_37.py rename to example_code/item_029.py index aa7585a..ca593cd 100755 --- a/example_code/item_37.py +++ b/example_code/item_029.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") class SimpleGradebook: def __init__(self): self._grades = {} @@ -62,17 +63,17 @@ def average_grade(self, name): return sum(grades) / len(grades) -# Example 2 +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) +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(book.average_grade("Isaac Newton")) -# Example 3 +print("Example 3") from collections import defaultdict class BySubjectGradebook: @@ -82,8 +83,6 @@ def __init__(self): 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] @@ -98,17 +97,17 @@ def average_grade(self, name): return total / count -# Example 5 +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')) +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 +print("Example 5") class WeightedGradebook: def __init__(self): self._grades = {} @@ -119,17 +118,15 @@ def add_student(self, name): def report_grade(self, name, subject, score, weight): by_subject = self._grades[name] grade_list = by_subject[subject] - grade_list.append((score, weight)) - + grade_list.append((score, weight)) # Changed -# 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(): + for scores in by_subject.values(): subject_avg, total_weight = 0, 0 - for score, weight in scores: + for score, weight in scores: # Added inner loop subject_avg += score * weight total_weight += weight @@ -139,18 +136,18 @@ def average_grade(self, name): return score_sum / score_count -# Example 8 +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')) +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 +print("Example 7") grades = [] grades.append((95, 0.45)) grades.append((85, 0.55)) @@ -160,23 +157,26 @@ def average_grade(self, name): print(average_grade) -# Example 10 +print("Example 8") grades = [] -grades.append((95, 0.45, 'Great job')) -grades.append((85, 0.55, 'Better next time')) +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 +print("Example 9") +from dataclasses import dataclass -Grade = namedtuple('Grade', ('score', 'weight')) +@dataclass(frozen=True) +class Grade: + score: int + weight: float -# Example 12 +print("Example 10") class Subject: def __init__(self): self._grades = [] @@ -192,7 +192,7 @@ def average_grade(self): return total / total_weight -# Example 13 +print("Example 11") class Student: def __init__(self): self._subjects = defaultdict(Subject) @@ -208,7 +208,7 @@ def average_grade(self): return total / count -# Example 14 +print("Example 12") class Gradebook: def __init__(self): self._students = defaultdict(Student) @@ -217,14 +217,14 @@ def get_student(self, name): return self._students[name] -# Example 15 +print("Example 13") book = Gradebook() -albert = book.get_student('Albert Einstein') -math = albert.get_subject('Math') +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 = 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_19.py b/example_code/item_031.py similarity index 60% rename from example_code/item_19.py rename to example_code/item_031.py index 35f20c7..1535b70 100755 --- a/example_code/item_19.py +++ b/example_code/item_031.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") def get_stats(numbers): minimum = min(numbers) maximum = max(numbers) @@ -56,10 +57,10 @@ def get_stats(numbers): minimum, maximum = get_stats(lengths) # Two return values -print(f'Min: {minimum}, Max: {maximum}') +print(f"Min: {minimum}, Max: {maximum}") -# Example 2 +print("Example 2") first, second = 1, 2 assert first == 1 assert second == 2 @@ -72,7 +73,7 @@ def my_function(): assert second == 2 -# Example 3 +print("Example 3") def get_avg_ratio(numbers): average = sum(numbers) / len(numbers) scaled = [x / average for x in numbers] @@ -81,17 +82,13 @@ def get_avg_ratio(numbers): longest, *middle, shortest = get_avg_ratio(lengths) -print(f'Longest: {longest:>4.0%}') -print(f'Shortest: {shortest:>4.0%}') +print(f"Longest: {longest:>4.0%}") +print(f"Shortest: {shortest:>4.0%}") -# Example 4 -def get_stats(numbers): - minimum = min(numbers) - maximum = max(numbers) +print("Example 4") +def get_median(numbers): count = len(numbers) - average = sum(numbers) / count - sorted_numbers = sorted(numbers) middle = count // 2 if count % 2 == 0: @@ -100,13 +97,20 @@ def get_stats(numbers): 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(lengths) +minimum, maximum, average, median, count = get_stats_more(lengths) -print(f'Min: {minimum}, Max: {maximum}') -print(f'Average: {average}, Median: {median}, Count {count}') +print(f"Min: {minimum}, Max: {maximum}") +print(f"Average: {average}, Median: {median}, Count {count}") assert minimum == 60 assert maximum == 73 @@ -115,28 +119,63 @@ def get_stats(numbers): assert count == 10 # Verify odd count median -_, _, _, median, count = get_stats([1, 2, 3]) +_, _, _, median, count = get_stats_more([1, 2, 3]) assert median == 2 assert count == 3 -# Example 5 +print("Example 5") # Correct: -minimum, maximum, average, median, count = get_stats(lengths) +minimum, maximum, average, median, count = get_stats_more(lengths) # Oops! Median and average swapped: -minimum, maximum, median, average, count = get_stats(lengths) +minimum, maximum, median, average, count = get_stats_more(lengths) -# Example 6 -minimum, maximum, average, median, count = get_stats( +print("Example 6") +minimum, maximum, average, median, count = get_stats_more( lengths) minimum, maximum, average, median, count = \ - get_stats(lengths) + get_stats_more(lengths) (minimum, maximum, average, - median, count) = get_stats(lengths) + median, count) = get_stats_more(lengths) (minimum, maximum, average, median, count - ) = get_stats(lengths) + ) = 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_20.py b/example_code/item_032.py similarity index 69% rename from example_code/item_20.py rename to example_code/item_032.py index 4a9639b..859860e 100755 --- a/example_code/item_20.py +++ b/example_code/item_032.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,100 +44,82 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +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 -# Example 2 +print("Example 2") x, y = 1, 0 result = careful_divide(x, y) if result is None: - print('Invalid inputs') + print("Invalid inputs") else: - print('Result is %.1f' % result) + print(f"Result is {result:.1f}") -# Example 3 +print("Example 3") x, y = 0, 5 result = careful_divide(x, y) -if not result: - print('Invalid inputs') # This runs! But shouldn't +if not result: # Changed + print("Invalid inputs") # This runs! But shouldn't else: assert False -# Example 4 +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) -# Example 5 +print("Example 5") x, y = 5, 0 success, result = careful_divide(x, y) if not success: - print('Invalid inputs') + print("Invalid inputs") -# Example 6 +print("Example 6") x, y = 5, 0 _, result = careful_divide(x, y) if not result: - print('Invalid inputs') + print("Invalid inputs") -# Example 7 +print("Example 7") def careful_divide(a, b): try: return a / b - except ZeroDivisionError as e: - raise ValueError('Invalid inputs') + except ZeroDivisionError: + raise ValueError("Invalid inputs") # Changed -# Example 8 +print("Example 8") x, y = 5, 2 try: result = careful_divide(x, y) except ValueError: - print('Invalid inputs') + 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 + 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..a7c1648 --- /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: python3 -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_21.py b/example_code/item_033.py similarity index 79% rename from example_code/item_21.py rename to example_code/item_033.py index 10f97ae..0124f84 100755 --- a/example_code/item_21.py +++ b/example_code/item_033.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,44 +44,48 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +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) -# Example 2 +print("Example 2") numbers = [8, 3, 1, 2, 5, 4, 7, 6] group = {2, 3, 5, 7} sort_priority(numbers, group) print(numbers) -# Example 3 +print("Example 3") def sort_priority2(numbers, group): - found = False + found = False # Flag initial value + def helper(x): if x in group: - found = True # Seems simple + found = True # Flip the flag return (0, x) return (1, x) + numbers.sort(key=helper) - return found + return found # Flag final value -# Example 4 +print("Example 4") numbers = [8, 3, 1, 2, 5, 4, 7, 6] found = sort_priority2(numbers, group) -print('Found:', found) +print("Found:", found) print(numbers) -# Example 5 +print("Example 5") try: foo = does_not_exist * 5 except: @@ -90,40 +94,43 @@ def helper(x): assert False -# Example 6 +print("Example 6") def sort_priority2(numbers, group): - found = False # Scope: 'sort_priority2' + found = False # Scope: 'sort_priority2' + def helper(x): if x in group: - found = True # Scope: 'helper' -- Bad! + found = True # Scope: 'helper' -- Bad! return (0, x) return (1, x) + numbers.sort(key=helper) return found -# Example 7 +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 -# Example 8 +print("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] +print("Found:", found) +print(numbers) -# Example 9 -numbers = [8, 3, 1, 2, 5, 4, 7, 6] +print("Example 9") class Sorter: def __init__(self, group): self.group = group @@ -135,7 +142,10 @@ def __call__(self, x): 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) -assert sorter.found is True -assert numbers == [2, 3, 5, 7, 1, 4, 6, 8] +print("Found:", sorter.found) +print(numbers) diff --git a/example_code/item_22.py b/example_code/item_034.py similarity index 61% rename from example_code/item_22.py rename to example_code/item_034.py index 4b8abbd..1c54553 100755 --- a/example_code/item_22.py +++ b/example_code/item_034.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,38 +44,39 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +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}') + values_str = ", ".join(str(x) for x in values) + print(f"{message}: {values_str}") -log('My numbers are', [1, 2]) -log('Hi there', []) +log("My numbers are", [1, 2]) +log("Hi there", []) -# Example 2 -def log(message, *values): # The only difference +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}') + 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 +log("My numbers are", 1, 2) +log("Hi there") # Changed -# Example 3 +print("Example 3") favorites = [7, 33, 99] -log('Favorite colors', *favorites) +log("Favorite colors", *favorites) -# Example 4 +print("Example 4") def my_generator(): for i in range(10): yield i @@ -87,14 +88,14 @@ def my_func(*args): my_func(*it) -# Example 5 -def log(sequence, message, *values): +print("Example 5") +def log_seq(sequence, message, *values): if not values: - print(f'{sequence} - {message}') + print(f"{sequence} - {message}") else: - values_str = ', '.join(str(x) for x in values) - print(f'{sequence} - {message}: {values_str}') + 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 +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_23.py b/example_code/item_035.py similarity index 73% rename from example_code/item_23.py rename to example_code/item_035.py index 99c6fe8..0b5ab67 100755 --- a/example_code/item_23.py +++ b/example_code/item_035.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,23 +44,24 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") def remainder(number, divisor): return number % divisor assert remainder(20, 7) == 6 -# Example 2 +print("Example 2") remainder(20, 7) remainder(20, divisor=7) remainder(number=20, divisor=7) remainder(divisor=7, number=20) -# Example 3 +print("Example 3") try: # This will not compile source = """remainder(number=20, 7)""" @@ -71,7 +72,7 @@ def remainder(number, divisor): assert False -# Example 4 +print("Example 4") try: remainder(20, number=7) except: @@ -80,82 +81,89 @@ def remainder(number, divisor): assert False -# Example 5 +print("Example 5") my_kwargs = { - 'number': 20, - 'divisor': 7, + "number": 20, + "divisor": 7, } assert remainder(**my_kwargs) == 6 -# Example 6 +print("Example 6") my_kwargs = { - 'divisor': 7, + "divisor": 7, } assert remainder(number=20, **my_kwargs) == 6 -# Example 7 +print("Example 7") my_kwargs = { - 'number': 20, + "number": 20, } other_kwargs = { - 'divisor': 7, + "divisor": 7, } assert remainder(**my_kwargs, **other_kwargs) == 6 -# Example 8 +print("Example 8") def print_parameters(**kwargs): for key, value in kwargs.items(): - print(f'{key} = {value}') + print(f"{key} = {value}") print_parameters(alpha=1.5, beta=9, gamma=4) -# Example 9 +print("Example 9") def flow_rate(weight_diff, time_diff): return weight_diff / time_diff -weight_diff = 0.5 -time_diff = 3 +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(f"{flow:.3} kg per second") -# Example 10 +print("Example 10") def flow_rate(weight_diff, time_diff, period): return (weight_diff / time_diff) * period -# Example 11 +print("Example 11") flow_per_second = flow_rate(weight_diff, time_diff, 1) -# Example 12 -def flow_rate(weight_diff, time_diff, period=1): +print("Example 12") +def flow_rate(weight_diff, time_diff, period=1): # Changed return (weight_diff / time_diff) * period -# Example 13 +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) -# Example 14 -def flow_rate(weight_diff, time_diff, - period=1, units_per_kg=1): +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 -# Example 15 -pounds_per_hour = flow_rate(weight_diff, time_diff, - period=3600, units_per_kg=2.2) +print("Example 15") +pounds_per_hour = flow_rate( + weight_diff, + time_diff, + period=3600, + units_per_kg=2.2, +) print(pounds_per_hour) -# Example 16 +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_24.py b/example_code/item_036.py similarity index 75% rename from example_code/item_24.py rename to example_code/item_036.py index 124da9b..1a0c005 100755 --- a/example_code/item_24.py +++ b/example_code/item_036.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,21 +44,22 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") from time import sleep from datetime import datetime def log(message, when=datetime.now()): - print(f'{when}: {message}') + print(f"{when}: {message}") -log('Hi there!') +log("Hi there!") sleep(0.1) -log('Hello again!') +log("Hello again!") -# Example 2 +print("Example 2") def log(message, when=None): """Log a message with a timestamp. @@ -69,16 +70,16 @@ def log(message, when=None): """ if when is None: when = datetime.now() - print(f'{when}: {message}') + print(f"{when}: {message}") -# Example 3 -log('Hi there!') +print("Example 3") +log("Hi there!") sleep(0.1) -log('Hello again!') +log("Hello again!") -# Example 4 +print("Example 4") import json def decode(data, default={}): @@ -88,20 +89,20 @@ def decode(data, default={}): return default -# Example 5 -foo = decode('bad data') -foo['stuff'] = 5 -bar = decode('also bad') -bar['meep'] = 1 -print('Foo:', foo) -print('Bar:', bar) +print("Example 5") +foo = decode("bad data") +foo["stuff"] = 5 +bar = decode("also bad") +bar["meep"] = 1 +print("Foo:", foo) +print("Bar:", bar) -# Example 6 +print("Example 6") assert foo is bar -# Example 7 +print("Example 7") def decode(data, default=None): """Load JSON data from a string. @@ -113,16 +114,16 @@ def decode(data, default=None): try: return json.loads(data) except ValueError: - if default is None: + if default is None: # Check here 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) +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_24_example_09.py b/example_code/item_036_example_09.py similarity index 72% rename from example_code/item_24_example_09.py rename to example_code/item_036_example_09.py index 7cbaa71..2896986 100755 --- a/example_code/item_24_example_09.py +++ b/example_code/item_036_example_09.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,15 +16,13 @@ -# Example 9 -# Check types in this file with: python -m mypy +print("Example 9") +# Check types in this file with: python3 -m mypy from datetime import datetime from time import sleep -from typing import Optional -def log_typed(message: str, - when: Optional[datetime]=None) -> None: +def log_typed(message: str, when: datetime | None = None) -> None: """Log a message with a timestamp. Args: @@ -34,8 +32,10 @@ def log_typed(message: str, """ if when is None: when = datetime.now() - print(f'{when}: {message}') + print(f"{when}: {message}") -log_typed('Hi there!') + +log_typed("Hi there!") sleep(0.1) -log_typed('Hello again!') +log_typed("Hello again!") +log_typed("And one more time", when=datetime.now()) diff --git a/example_code/item_25.py b/example_code/item_037.py similarity index 66% rename from example_code/item_25.py rename to example_code/item_037.py index 2f1cce2..7da1cb5 100755 --- a/example_code/item_25.py +++ b/example_code/item_037.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,12 +44,16 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 -def safe_division(number, divisor, - ignore_overflow, - ignore_zero_division): +print("Example 1") +def safe_division( + number, + divisor, + ignore_overflow, + ignore_zero_division, +): try: return number / divisor except OverflowError: @@ -59,25 +63,28 @@ def safe_division(number, divisor, raise except ZeroDivisionError: if ignore_zero_division: - return float('inf') + return float("inf") else: raise -# Example 2 +print("Example 2") result = safe_division(1.0, 10**500, True, False) print(result) -# Example 3 +print("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 +print("Example 4") +def safe_division_b( + number, + divisor, + ignore_overflow=False, # Changed + ignore_zero_division=False, # Changed +): try: return number / divisor except OverflowError: @@ -87,12 +94,12 @@ def safe_division_b(number, divisor, raise except ZeroDivisionError: if ignore_zero_division: - return float('inf') + return float("inf") else: raise -# Example 5 +print("Example 5") result = safe_division_b(1.0, 10**500, ignore_overflow=True) print(result) @@ -100,14 +107,18 @@ def safe_division_b(number, divisor, print(result) -# Example 6 +print("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): +print("Example 7") +def safe_division_c( + number, + divisor, + *, # Added + ignore_overflow=False, + ignore_zero_division=False, +): try: return number / divisor except OverflowError: @@ -117,12 +128,12 @@ def safe_division_c(number, divisor, *, # Changed raise except ZeroDivisionError: if ignore_zero_division: - return float('inf') + return float("inf") else: raise -# Example 8 +print("Example 8") try: safe_division_c(1.0, 10**500, True, False) except: @@ -131,9 +142,9 @@ def safe_division_c(number, divisor, *, # Changed assert False -# Example 9 +print("Example 9") result = safe_division_c(1.0, 0, ignore_zero_division=True) -assert result == float('inf') +assert result == float("inf") try: result = safe_division_c(1.0, 0) @@ -143,16 +154,20 @@ def safe_division_c(number, divisor, *, # Changed assert False -# Example 10 +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 -# Example 11 -def safe_division_c(numerator, denominator, *, # Changed - ignore_overflow=False, - ignore_zero_division=False): +print("Example 11") +def safe_division_d( + numerator, # Changed + denominator, # Changed + *, + ignore_overflow=False, + ignore_zero_division=False +): try: return numerator / denominator except OverflowError: @@ -162,24 +177,29 @@ def safe_division_c(numerator, denominator, *, # Changed raise except ZeroDivisionError: if ignore_zero_division: - return float('inf') + return float("inf") else: raise -# Example 12 +print("Example 12") try: - safe_division_c(number=2, divisor=5) + safe_division_d(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): +print("Example 13") +def safe_division_e( + numerator, + denominator, + /, # Added + *, + ignore_overflow=False, + ignore_zero_division=False, +): try: return numerator / denominator except OverflowError: @@ -189,32 +209,37 @@ def safe_division_d(numerator, denominator, /, *, # Changed raise except ZeroDivisionError: if ignore_zero_division: - return float('inf') + return float("inf") else: raise -# Example 14 -assert safe_division_d(2, 5) == 0.4 +print("Example 14") +assert safe_division_e(2, 5) == 0.4 -# Example 15 +print("Example 15") try: - safe_division_d(numerator=2, denominator=5) + safe_division_e(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): +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 + fraction = numerator / denominator # Changed + return round(fraction, ndigits) # Changed except OverflowError: if ignore_overflow: return 0 @@ -222,17 +247,17 @@ def safe_division_e(numerator, denominator, /, raise except ZeroDivisionError: if ignore_zero_division: - return float('inf') + return float("inf") else: raise -# Example 17 -result = safe_division_e(22, 7) +print("Example 17") +result = safe_division_f(22, 7) print(result) -result = safe_division_e(22, 7, 5) +result = safe_division_f(22, 7, 5) print(result) -result = safe_division_e(22, 7, ndigits=2) +result = safe_division_f(22, 7, ndigits=2) print(result) diff --git a/example_code/item_26.py b/example_code/item_038.py similarity index 74% rename from example_code/item_26.py rename to example_code/item_038.py index 105b47b..855930c 100755 --- a/example_code/item_26.py +++ b/example_code/item_038.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,28 +44,33 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +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__}({args!r}, {kwargs!r}) ' - f'-> {result!r}') + print(f"{func.__name__}" + f"({args_repr}, {kwargs_repr}) " + f"-> {result!r}") return result + return wrapper -# Example 2 +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)) + return fibonacci(n - 2) + fibonacci(n - 1) -# Example 3 +print("Example 3") def fibonacci(n): """Return the n-th Fibonacci number""" if n in (0, 1): @@ -75,19 +80,19 @@ def fibonacci(n): fibonacci = trace(fibonacci) -# Example 4 +print("Example 4") fibonacci(4) -# Example 5 +print("Example 5") print(fibonacci) -# Example 6 +print("Example 6") help(fibonacci) -# Example 7 +print("Example 7") try: import pickle @@ -98,16 +103,18 @@ def fibonacci(n): assert False -# Example 8 +print("Example 8") from functools import wraps def trace(func): - @wraps(func) + @wraps(func) # Changed def wrapper(*args, **kwargs): + args_repr = repr(args) + kwargs_repr = repr(kwargs) result = func(*args, **kwargs) - print(f'{func.__name__}({args!r}, {kwargs!r}) ' - f'-> {result!r}') + print(f"{func.__name__}" f"({args_repr}, {kwargs_repr}) " f"-> {result!r}") return result + return wrapper @trace @@ -118,9 +125,9 @@ def fibonacci(n): return fibonacci(n - 2) + fibonacci(n - 1) -# Example 9 +print("Example 9") help(fibonacci) -# Example 10 +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_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_27.py b/example_code/item_040.py similarity index 76% rename from example_code/item_27.py rename to example_code/item_040.py index c1a4847..24015b5 100755 --- a/example_code/item_27.py +++ b/example_code/item_040.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] squares = [] for x in a: @@ -54,37 +55,45 @@ def close_open_files(): print(squares) -# Example 2 +print("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}' +print("Example 3") +alt = map(lambda x: x**2, a) +assert list(alt) == squares, f"{alt} {squares}" -# Example 4 +print("Example 4") even_squares = [x**2 for x in a if x % 2 == 0] print(even_squares) -# Example 5 +print("Example 5") alt = map(lambda x: x**2, filter(lambda x: x % 2 == 0, a)) assert even_squares == list(alt) -# Example 6 +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) -# 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))) +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_28.py b/example_code/item_041.py similarity index 82% rename from example_code/item_28.py rename to example_code/item_041.py index d1efe24..8310508 100755 --- a/example_code/item_28.py +++ b/example_code/item_041.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,20 +44,25 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 -matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] +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) -# Example 2 +print("Example 2") squared = [[x**2 for x in row] for row in matrix] print(squared) -# Example 3 +print("Example 3") my_lists = [ [[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]], @@ -68,7 +73,7 @@ def close_open_files(): print(flat) -# Example 4 +print("Example 4") flat = [] for sublist1 in my_lists: for sublist2 in sublist1: @@ -76,7 +81,7 @@ def close_open_files(): print(flat) -# Example 5 +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] @@ -86,8 +91,12 @@ def close_open_files(): 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] +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_29.py b/example_code/item_042.py similarity index 69% rename from example_code/item_29.py rename to example_code/item_042.py index 207faf1..145b428 100755 --- a/example_code/item_29.py +++ b/example_code/item_042.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,54 +44,55 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") stock = { - 'nails': 125, - 'screws': 35, - 'wingnuts': 8, - 'washers': 24, + "nails": 125, + "screws": 35, + "wingnuts": 8, + "washers": 24, } -order = ['screws', 'wingnuts', 'clips'] +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 + count = stock.get(name, 0) + batches = get_batches(count, 8) + if batches: + result[name] = batches print(result) -# Example 2 +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) -# Example 3 -has_bug = {name: get_batches(stock.get(name, 0), 4) +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("Expected:", found) +print("Found: ", has_bug) -# Example 4 +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 +assert found == {"screws": 4, "wingnuts": 1}, found -# Example 5 +print("Example 5") try: result = {name: (tenth := count // 10) for name, count in stock.items() if tenth > 0} @@ -101,24 +102,28 @@ def get_batches(count, size): assert False -# Example 6 +print("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}') +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}") -# Example 8 -for count in stock.values(): # Leaks loop variable - pass -print(f'Last item of {list(stock.values())} is {count}') +print("Example 8") +for count in stock.values(): + last = count // 2 + squared = last**2 +print(f"{count} // 2 = {last}; {last} ** 2 = {squared}") -# Example 9 + +print("Example 9") try: del count half = [count // 2 for count in stock.values()] @@ -130,7 +135,7 @@ def get_batches(count, size): assert False -# Example 10 +print("Example 10") found = ((name, batches) for name in order if (batches := get_batches(stock.get(name, 0), 8))) print(next(found)) diff --git a/example_code/item_30.py b/example_code/item_043.py similarity index 79% rename from example_code/item_30.py rename to example_code/item_043.py index ba4fa9b..99fab75 100755 --- a/example_code/item_30.py +++ b/example_code/item_043.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,47 +44,48 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") def index_words(text): result = [] if text: result.append(0) for index, letter in enumerate(text): - if letter == ' ': + 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.' +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]) -# Example 3 +print("Example 3") def index_words_iter(text): if text: yield 0 for index, letter in enumerate(text): - if letter == ' ': + if letter == " ": yield index + 1 -# Example 4 +print("Example 4") it = index_words_iter(address) print(next(it)) print(next(it)) -# Example 5 +print("Example 5") result = list(index_words_iter(address)) print(result[:10]) -# Example 6 +print("Example 6") def index_file(handle): offset = 0 for line in handle: @@ -92,22 +93,23 @@ def index_file(handle): yield offset for letter in line: offset += 1 - if letter == ' ': + if letter == " ": yield offset -# Example 7 +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: +with open("address.txt", "w") as f: f.write(address_lines) import itertools -with open('address.txt', 'r') as f: + +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_32.py b/example_code/item_044.py similarity index 76% rename from example_code/item_32.py rename to example_code/item_044.py index 1cefe9b..a35ba2d 100755 --- a/example_code/item_32.py +++ b/example_code/item_044.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,33 +44,34 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") import random -with open('my_file.txt', 'w') as f: +with open("my_file.txt", "w") as f: for _ in range(10): - f.write('a' * random.randint(0, 100)) - f.write('\n') + f.write("a" * random.randint(0, 100)) + f.write("\n") -value = [len(x) for x in open('my_file.txt')] +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("Example 2") +it = (len(x) for x in open("my_file.txt")) print(it) -# Example 3 +print("Example 3") print(next(it)) print(next(it)) -# Example 4 +print("Example 4") roots = ((x, x**0.5) for x in it) -# Example 5 +print("Example 5") print(next(roots)) diff --git a/example_code/item_33.py b/example_code/item_045.py similarity index 71% rename from example_code/item_33.py rename to example_code/item_045.py index d7bd5c6..344fedd 100755 --- a/example_code/item_33.py +++ b/example_code/item_045.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") def move(period, speed): for _ in range(period): yield speed @@ -56,7 +57,7 @@ def pause(delay): yield 0 -# Example 2 +print("Example 2") def animate(): for delta in move(4, 5.0): yield delta @@ -66,9 +67,9 @@ def animate(): yield delta -# Example 3 +print("Example 3") def render(delta): - print(f'Delta: {delta:.1f}') + print(f"Delta: {delta:.1f}") # Move the images onscreen def run(func): @@ -78,40 +79,10 @@ def run(func): run(animate) -# Example 4 +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) - - -# 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_046.py similarity index 78% rename from example_code/item_34.py rename to example_code/item_046.py index 6a82a35..b7a33de 100755 --- a/example_code/item_34.py +++ b/example_code/item_046.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") import math def wave(amplitude, steps): @@ -58,12 +59,12 @@ def wave(amplitude, steps): yield output -# Example 2 +print("Example 2") def transmit(output): if output is None: - print(f'Output is None') + print(f"Output is None") else: - print(f'Output: {output:>5.1f}') + print(f"Output: {output:>5.1f}") def run(it): for output in it: @@ -72,51 +73,50 @@ def run(it): run(wave(3.0, 8)) -# Example 3 +print("Example 3") def my_generator(): received = yield 1 - print(f'received = {received}') + print(f"{received=}") it = my_generator() -output = next(it) # Get first generator output -print(f'output = {output}') +output = next(it) # Get first generator output +print(f"{output=}") try: - next(it) # Run generator until it exits + next(it) # Run generator until it exits except StopIteration: pass else: assert False -# Example 4 +print("Example 4") it = my_generator() output = it.send(None) # Get first generator output -print(f'output = {output}') +print(f"{output=}") try: - it.send('hello!') # Send value into the generator + it.send("hello!") # Send value into the generator except StopIteration: pass else: assert False -# Example 5 +print("Example 5") def wave_modulating(steps): step_size = 2 * math.pi / steps - amplitude = yield # Receive initial amplitude + 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 + amplitude = yield output # Receive next amplitude -# Example 6 +print("Example 6") def run_modulating(it): - amplitudes = [ - None, 7, 7, 7, 2, 2, 2, 2, 10, 10, 10, 10, 10] + amplitudes = [None, 7, 7, 7, 2, 2, 2, 2, 10, 10, 10, 10, 10] for amplitude in amplitudes: output = it.send(amplitude) transmit(output) @@ -124,7 +124,7 @@ def run_modulating(it): run_modulating(wave_modulating(12)) -# Example 7 +print("Example 7") def complex_wave(): yield from wave(7.0, 3) yield from wave(2.0, 4) @@ -133,7 +133,7 @@ def complex_wave(): run(complex_wave()) -# Example 8 +print("Example 8") def complex_wave_modulating(): yield from wave_modulating(3) yield from wave_modulating(4) @@ -142,7 +142,7 @@ def complex_wave_modulating(): run_modulating(complex_wave_modulating()) -# Example 9 +print("Example 9") def wave_cascading(amplitude_it, steps): step_size = 2 * math.pi / steps for step in range(steps): @@ -153,17 +153,17 @@ def wave_cascading(amplitude_it, steps): yield output -# Example 10 +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) -# Example 11 +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)) + it = complex_wave_cascading(iter(amplitudes)) # Supplies iterator for amplitude in amplitudes: output = next(it) transmit(output) diff --git a/example_code/item_35.py b/example_code/item_047.py similarity index 66% rename from example_code/item_35.py rename to example_code/item_047.py index 1d53234..1cc5a1a 100755 --- a/example_code/item_35.py +++ b/example_code/item_047.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") try: class MyError(Exception): pass @@ -57,62 +58,78 @@ def my_generator(): yield 3 it = my_generator() - print(next(it)) # Yield 1 - print(next(it)) # Yield 2 - print(it.throw(MyError('test error'))) + print(next(it)) # Yields 1 + print(next(it)) # Yields 2 + print(it.throw(MyError("test error"))) # Raises except: logging.exception('Expected') else: assert False -# Example 2 +print("Example 2") def my_generator(): yield 1 try: yield 2 except MyError: - print('Got 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'))) +print(next(it)) # Yields 1 +print(next(it)) # Yields 2 +print(it.throw(MyError("test error"))) # Yields 4 -# Example 3 +print("Example 3") class Reset(Exception): pass def timer(period): current = period while current: - current -= 1 try: yield current except Reset: + print("Resetting") current = period - - -# Example 4 -RESETS = [ - False, False, False, True, False, True, False, - False, False, False, False, False, False, False] + 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') + print(f"{remaining} ticks remaining") def run(): - it = timer(4) + it = timer(4) while True: try: if check_for_reset(): @@ -127,31 +144,34 @@ def run(): run() -# Example 5 +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 __iter__(self): - while self.current: - self.current -= 1 - yield self.current + def tick(self): + before = self.current + self.current -= 1 + return before + def __bool__(self): + return self.current > 0 -# Example 6 -RESETS = [ - False, False, True, False, True, False, - False, False, False, False, False, False, False] + +print("Example 6") +RESETS = ORIGINAL_RESETS[:] def run(): timer = Timer(4) - for current in timer: + while timer: if check_for_reset(): timer.reset() - announce(current) + + announce(timer.tick()) run() diff --git a/example_code/item_38.py b/example_code/item_048.py similarity index 83% rename from example_code/item_38.py rename to example_code/item_048.py index f64d30a..902b8fc 100755 --- a/example_code/item_38.py +++ b/example_code/item_048.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,37 +44,38 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 -names = ['Socrates', 'Archimedes', 'Plato', 'Aristotle'] +print("Example 1") +names = ["Socrates", "Archimedes", "Plato", "Aristotle"] names.sort(key=len) print(names) -# Example 2 +print("Example 2") def log_missing(): - print('Key added') + print("Key added") return 0 -# Example 3 +print("Example 3") from collections import defaultdict -current = {'green': 12, 'blue': 3} +current = {"green": 12, "blue": 3} increments = [ - ('red', 5), - ('blue', 17), - ('orange', 9), + ("red", 5), + ("blue", 17), + ("orange", 9), ] result = defaultdict(log_missing, current) -print('Before:', dict(result)) +print("Before:", dict(result)) for key, amount in increments: result[key] += amount -print('After: ', dict(result)) +print("After: ", dict(result)) -# Example 4 +print("Example 4") def increment_with_report(current, increments): added_count = 0 @@ -90,13 +91,13 @@ def missing(): return result, added_count -# Example 5 +print("Example 5") result, count = increment_with_report(current, increments) assert count == 2 print(result) -# Example 6 +print("Example 6") class CountMissing: def __init__(self): self.added = 0 @@ -106,7 +107,7 @@ def missing(self): return 0 -# Example 7 +print("Example 7") counter = CountMissing() result = defaultdict(counter.missing, current) # Method ref for key, amount in increments: @@ -115,7 +116,7 @@ def missing(self): print(result) -# Example 8 +print("Example 8") class BetterCountMissing: def __init__(self): self.added = 0 @@ -129,7 +130,7 @@ def __call__(self): assert callable(counter) -# Example 9 +print("Example 9") counter = BetterCountMissing() result = defaultdict(counter, current) # Relies on __call__ for key, amount in increments: 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..3d9fd9f --- /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: python3 -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_90_example_17.py b/example_code/item_051_example_05.py similarity index 62% rename from example_code/item_90_example_17.py rename to example_code/item_051_example_05.py index b77f9fc..1d51199 100755 --- a/example_code/item_90_example_17.py +++ b/example_code/item_051_example_05.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,16 +16,17 @@ -# Example 17 -from __future__ import annotations +print("Example 5") +# Check types in this file with: python3 -m mypy -class FirstClass: - def __init__(self, value: SecondClass) -> None: # OK - self.value = value +class RGB: + def __init__( + self, red: int, green: int, blue: int + ) -> None: # Changed + self.red = red + self.green = green + self.blue = blue -class SecondClass: - def __init__(self, value: int) -> None: - self.value = value -second = SecondClass(5) -first = FirstClass(second) +obj = RGB(1, "bad", 3) +obj.red = "also bad" diff --git a/example_code/item_39.py b/example_code/item_052.py similarity index 81% rename from example_code/item_39.py rename to example_code/item_052.py index 60ae575..acffdf5 100755 --- a/example_code/item_39.py +++ b/example_code/item_052.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,15 +44,16 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") class InputData: def read(self): raise NotImplementedError -# Example 2 +print("Example 2") class PathInputData(InputData): def __init__(self, path): super().__init__() @@ -63,7 +64,7 @@ def read(self): return f.read() -# Example 3 +print("Example 3") class Worker: def __init__(self, input_data): self.input_data = input_data @@ -76,17 +77,17 @@ def reduce(self, other): raise NotImplementedError -# Example 4 +print("Example 4") class LineCountWorker(Worker): def map(self): data = self.input_data.read() - self.result = data.count('\n') + self.result = data.count("\n") def reduce(self, other): self.result += other.result -# Example 5 +print("Example 5") import os def generate_inputs(data_dir): @@ -94,7 +95,7 @@ def generate_inputs(data_dir): yield PathInputData(os.path.join(data_dir, name)) -# Example 6 +print("Example 6") def create_workers(input_list): workers = [] for input_data in input_list: @@ -102,13 +103,15 @@ def create_workers(input_list): return workers -# Example 7 +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() + for thread in threads: + thread.start() + for thread in threads: + thread.join() first, *rest = workers for worker in rest: @@ -116,31 +119,31 @@ def execute(workers): return first.result -# Example 8 +print("Example 8") def mapreduce(data_dir): inputs = generate_inputs(data_dir) workers = create_workers(inputs) return execute(workers) -# Example 9 +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)) + with open(os.path.join(tmpdir, str(i)), "w") as f: + f.write("\n" * random.randint(0, 100)) -tmpdir = 'test_inputs' +tmpdir = "test_inputs" write_test_files(tmpdir) result = mapreduce(tmpdir) -print(f'There are {result} lines') +print(f"There are {result} lines") -# Example 10 +print("Example 10") class GenericInputData: def read(self): raise NotImplementedError @@ -150,7 +153,7 @@ def generate_inputs(cls, config): raise NotImplementedError -# Example 11 +print("Example 11") class PathInputData(GenericInputData): def __init__(self, path): super().__init__() @@ -160,14 +163,15 @@ def read(self): with open(self.path) as f: return f.read() + @classmethod def generate_inputs(cls, config): - data_dir = config['data_dir'] + data_dir = config["data_dir"] for name in os.listdir(data_dir): yield cls(os.path.join(data_dir, name)) -# Example 12 +print("Example 12") class GenericWorker: def __init__(self, input_data): self.input_data = input_data @@ -187,23 +191,23 @@ def create_workers(cls, input_class, config): return workers -# Example 13 -class LineCountWorker(GenericWorker): +print("Example 13") +class LineCountWorker(GenericWorker): # Changed def map(self): data = self.input_data.read() - self.result = data.count('\n') + self.result = data.count("\n") def reduce(self, other): self.result += other.result -# Example 14 +print("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} +print("Example 15") +config = {"data_dir": tmpdir} result = mapreduce(LineCountWorker, PathInputData, config) -print(f'There are {result} lines') +print(f"There are {result} lines") diff --git a/example_code/item_40.py b/example_code/item_053.py similarity index 83% rename from example_code/item_40.py rename to example_code/item_053.py index 136eeb0..59495f5 100755 --- a/example_code/item_40.py +++ b/example_code/item_053.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") class MyBaseClass: def __init__(self, value): self.value = value @@ -55,14 +56,8 @@ 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 +print("Example 2") class TimesTwo: def __init__(self): self.value *= 2 @@ -72,7 +67,7 @@ def __init__(self): self.value += 5 -# Example 3 +print("Example 3") class OneWay(MyBaseClass, TimesTwo, PlusFive): def __init__(self, value): MyBaseClass.__init__(self, value) @@ -80,12 +75,12 @@ def __init__(self, value): PlusFive.__init__(self) -# Example 4 +print("Example 4") foo = OneWay(5) -print('First ordering value is (5 * 2) + 5 =', foo.value) +print("First ordering value is (5 * 2) + 5 =", foo.value) -# Example 5 +print("Example 5") class AnotherWay(MyBaseClass, PlusFive, TimesTwo): def __init__(self, value): MyBaseClass.__init__(self, value) @@ -93,12 +88,12 @@ def __init__(self, value): PlusFive.__init__(self) -# Example 6 +print("Example 6") bar = AnotherWay(5) -print('Second ordering value is', bar.value) +print("Second ordering should be (5 + 5) * 2, but is", bar.value) -# Example 7 +print("Example 7") class TimesSeven(MyBaseClass): def __init__(self, value): MyBaseClass.__init__(self, value) @@ -110,17 +105,17 @@ def __init__(self, value): self.value += 9 -# Example 8 +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("Should be (5 * 7) + 9 = 44 but is", foo.value) -# Example 9 +print("Example 9") class MyBaseClass: def __init__(self, value): self.value = value @@ -136,29 +131,30 @@ def __init__(self, value): self.value += 9 -# Example 10 +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("Should be 7 * (5 + 9) = 98 and is", foo.value) -# Example 11 -mro_str = '\n'.join(repr(cls) for cls in GoodWay.mro()) +print("Example 11") +mro_str = "\n".join(repr(cls) for cls in GoodWay.__mro__) print(mro_str) -# Example 12 +print("Example 12") class ExplicitTrisect(MyBaseClass): def __init__(self, value): super(ExplicitTrisect, self).__init__(value) self.value /= 3 + assert ExplicitTrisect(9).value == 3 -# Example 13 +print("Example 13") class AutomaticTrisect(MyBaseClass): def __init__(self, value): super(__class__, self).__init__(value) diff --git a/example_code/item_41.py b/example_code/item_054.py similarity index 85% rename from example_code/item_41.py rename to example_code/item_054.py index ce8bfab..4e6acfe 100755 --- a/example_code/item_41.py +++ b/example_code/item_054.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,15 +44,14 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("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(): @@ -66,13 +65,13 @@ def _traverse(self, key, value): return self._traverse_dict(value) elif isinstance(value, list): return [self._traverse(key, i) for i in value] - elif hasattr(value, '__dict__'): + elif hasattr(value, "__dict__"): return self._traverse_dict(value.__dict__) else: return value -# Example 3 +print("Example 2") class BinaryTree(ToDictMixin): def __init__(self, value, left=None, right=None): self.value = value @@ -80,34 +79,41 @@ def __init__(self, value, left=None, right=None): self.right = right -# Example 4 -tree = BinaryTree(10, +print("Example 3") +tree = BinaryTree( + 10, left=BinaryTree(7, right=BinaryTree(9)), - right=BinaryTree(13, left=BinaryTree(11))) + right=BinaryTree(13, left=BinaryTree(11)), +) orig_print = print print = pprint print(tree.to_dict()) print = orig_print -# Example 5 +print("Example 4") class BinaryTreeWithParent(BinaryTree): - def __init__(self, value, left=None, - right=None, parent=None): + 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'): + if ( + isinstance(value, BinaryTreeWithParent) + and key == "parent" + ): return value.value # Prevent cycles else: return super()._traverse(key, value) -# Example 7 +print("Example 5") root = BinaryTreeWithParent(10) root.left = BinaryTreeWithParent(7, parent=root) root.left.right = BinaryTreeWithParent(9, parent=root.left) @@ -117,20 +123,20 @@ def _traverse(self, key, value): print = orig_print -# Example 8 +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) +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 +print("Example 7") import json class JsonMixin: @@ -143,7 +149,7 @@ def to_json(self): return json.dumps(self.to_dict()) -# Example 10 +print("Example 8") class DatacenterRack(ToDictMixin, JsonMixin): def __init__(self, switch=None, machines=None): self.switch = Switch(**switch) @@ -162,7 +168,7 @@ def __init__(self, cores=None, ram=None, disk=None): self.disk = disk -# Example 11 +print("Example 9") serialized = """{ "switch": {"ports": 5, "speed": 1e9}, "machines": [ diff --git a/example_code/item_42.py b/example_code/item_055.py similarity index 83% rename from example_code/item_42.py rename to example_code/item_055.py index f6dd9fd..5cb93a7 100755 --- a/example_code/item_42.py +++ b/example_code/item_055.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") class MyObject: def __init__(self): self.public_field = 5 @@ -56,16 +57,16 @@ def get_private_field(self): return self.__private_field -# Example 2 +print("Example 2") foo = MyObject() assert foo.public_field == 5 -# Example 3 +print("Example 3") assert foo.get_private_field() == 10 -# Example 4 +print("Example 4") try: foo.__private_field except: @@ -74,7 +75,7 @@ def get_private_field(self): assert False -# Example 5 +print("Example 5") class MyOtherObject: def __init__(self): self.__private_field = 71 @@ -87,7 +88,7 @@ def get_private_field_of_instance(cls, instance): assert MyOtherObject.get_private_field_of_instance(bar) == 71 -# Example 6 +print("Example 6") try: class MyParentObject: def __init__(self): @@ -105,15 +106,15 @@ def get_private_field(self): assert False -# Example 7 +print("Example 7") assert baz._MyParentObject__private_field == 71 -# Example 8 +print("Example 8") print(baz.__dict__) -# Example 9 +print("Example 9") class MyStringClass: def __init__(self, value): self.__value = value @@ -122,19 +123,19 @@ def get_value(self): return str(self.__value) foo = MyStringClass(5) -assert foo.get_value() == '5' +assert foo.get_value() == "5" -# Example 10 +print("Example 10") class MyIntegerSubclass(MyStringClass): def get_value(self): return int(self._MyStringClass__value) -foo = MyIntegerSubclass('5') +foo = MyIntegerSubclass("5") assert foo.get_value() == 5 -# Example 11 +print("Example 11") class MyBaseClass: def __init__(self, value): self.__value = value @@ -151,7 +152,7 @@ def get_value(self): return int(self._MyStringClass__value) # Not updated -# Example 12 +print("Example 12") try: foo = MyIntegerSubclass(5) foo.get_value() @@ -161,7 +162,7 @@ def get_value(self): assert False -# Example 13 +print("Example 13") class MyStringClass: def __init__(self, value): # This stores the user-supplied value for the object. @@ -172,6 +173,8 @@ def __init__(self, value): def get_value(self): return str(self._value) + + class MyIntegerSubclass(MyStringClass): def get_value(self): return self._value @@ -180,7 +183,7 @@ def get_value(self): assert foo.get_value() == 5 -# Example 14 +print("Example 14") class ApiClass: def __init__(self): self._value = 5 @@ -191,24 +194,24 @@ def get(self): class Child(ApiClass): def __init__(self): super().__init__() - self._value = 'hello' # Conflicts + self._value = "hello" # Conflicts a = Child() -print(f'{a.get()} and {a._value} should be different') +print(f"{a.get()} and {a._value} should be different") -# Example 15 +print("Example 15") class ApiClass: def __init__(self): - self.__value = 5 # Double underscore + self.__value = 5 # Double underscore def get(self): - return self.__value # Double underscore + return self.__value # Double underscore class Child(ApiClass): def __init__(self): super().__init__() - self._value = 'hello' # OK! + self._value = "hello" # OK! a = Child() -print(f'{a.get()} and {a._value} are different') +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..eba031e --- /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: python3 -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..867579e --- /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: python3 -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_43.py b/example_code/item_057.py similarity index 68% rename from example_code/item_43.py rename to example_code/item_057.py index 5a842ac..5f60b20 100755 --- a/example_code/item_43.py +++ b/example_code/item_057.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") class FrequencyList(list): def __init__(self, members): super().__init__(members) @@ -58,15 +59,15 @@ def frequency(self): 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()) +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()) -# Example 3 +print("Example 3") class BinaryNode: def __init__(self, value, left=None, right=None): self.value = value @@ -74,16 +75,16 @@ def __init__(self, value, left=None, right=None): self.right = right -# Example 4 +print("Example 4") bar = [1, 2, 3] bar[0] -# Example 5 +print("Example 5") bar.__getitem__(0) -# Example 6 +print("Example 6") class IndexableNode(BinaryNode): def _traverse(self): if self.left is not None: @@ -96,30 +97,28 @@ 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') + raise IndexError(f"Index {index} is out of range") -# Example 7 +print("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)) + 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] @@ -129,7 +128,7 @@ def __getitem__(self, index): assert False -# Example 9 +print("Example 9") try: len(tree) except: @@ -138,32 +137,30 @@ def __getitem__(self, index): assert False -# Example 10 +print("Example 10") class SequenceNode(IndexableNode): def __len__(self): - for count, _ in enumerate(self._traverse(), 1): - pass + count = 0 + for _ in self._traverse(): + count += 1 return count -# Example 11 +print("Example 11") tree = SequenceNode( 10, left=SequenceNode( 5, left=SequenceNode(2), - right=SequenceNode( - 6, - right=SequenceNode(7))), - right=SequenceNode( - 15, - left=SequenceNode(11)) + right=SequenceNode(6, right=SequenceNode(7)), + ), + right=SequenceNode(15, left=SequenceNode(11)), ) -print('Tree length is', len(tree)) +print("Tree length is", len(tree)) -# Example 12 +print("Example 12") try: # Make sure that this doesn't work tree.count(4) @@ -173,7 +170,7 @@ def __len__(self): assert False -# Example 13 +print("Example 13") try: from collections.abc import Sequence @@ -187,7 +184,7 @@ class BadType(Sequence): assert False -# Example 14 +print("Example 14") class BetterNode(SequenceNode, Sequence): pass @@ -196,13 +193,10 @@ class BetterNode(SequenceNode, Sequence): left=BetterNode( 5, left=BetterNode(2), - right=BetterNode( - 6, - right=BetterNode(7))), - right=BetterNode( - 15, - left=BetterNode(11)) + 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)) +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_058.py similarity index 79% rename from example_code/item_44.py rename to example_code/item_058.py index a2062f5..776d49e 100755 --- a/example_code/item_44.py +++ b/example_code/item_058.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") class OldResistor: def __init__(self, ohms): self._ohms = ohms @@ -58,19 +59,19 @@ def set_ohms(self, ohms): self._ohms = ohms -# Example 2 +print("Example 2") r0 = OldResistor(50e3) -print('Before:', r0.get_ohms()) +print("Before:", r0.get_ohms()) r0.set_ohms(10e3) -print('After: ', r0.get_ohms()) +print("After: ", r0.get_ohms()) -# Example 3 +print("Example 3") r0.set_ohms(r0.get_ohms() - 4e3) assert r0.get_ohms() == 6e3 -# Example 4 +print("Example 4") class Resistor: def __init__(self, ohms): self.ohms = ohms @@ -79,16 +80,16 @@ def __init__(self, ohms): r1 = Resistor(50e3) r1.ohms = 10e3 -print(f'{r1.ohms} ohms, ' - f'{r1.voltage} volts, ' - f'{r1.current} amps') +print( + f"{r1.ohms} ohms, " f"{r1.voltage} volts, " f"{r1.current} amps" +) -# Example 5 +print("Example 5") r1.ohms += 5e3 -# Example 6 +print("Example 6") class VoltageResistance(Resistor): def __init__(self, ohms): super().__init__(ohms) @@ -104,14 +105,14 @@ def voltage(self, voltage): self.current = self._voltage / self.ohms -# Example 7 -r2 = VoltageResistance(1e3) -print(f'Before: {r2.current:.2f} amps') +print("Example 7") +r2 = VoltageResistance(1e2) +print(f"Before: {r2.current:.2f} amps") r2.voltage = 10 -print(f'After: {r2.current:.2f} amps') +print(f"After: {r2.current:.2f} amps") -# Example 8 +print("Example 8") class BoundedResistance(Resistor): def __init__(self, ohms): super().__init__(ohms) @@ -123,11 +124,11 @@ def ohms(self): @ohms.setter def ohms(self, ohms): if ohms <= 0: - raise ValueError(f'ohms must be > 0; got {ohms}') + raise ValueError(f"ohms must be > 0; got {ohms}") self._ohms = ohms -# Example 9 +print("Example 9") try: r3 = BoundedResistance(1e3) r3.ohms = 0 @@ -137,7 +138,7 @@ def ohms(self, ohms): assert False -# Example 10 +print("Example 10") try: BoundedResistance(-5) except: @@ -146,7 +147,7 @@ def ohms(self, ohms): assert False -# Example 11 +print("Example 11") class FixedResistance(Resistor): def __init__(self, ohms): super().__init__(ohms) @@ -157,12 +158,12 @@ def ohms(self): @ohms.setter def ohms(self, ohms): - if hasattr(self, '_ohms'): + if hasattr(self, "_ohms"): raise AttributeError("Ohms is immutable") self._ohms = ohms -# Example 12 +print("Example 12") try: r4 = FixedResistance(1e3) r4.ohms = 2e3 @@ -172,7 +173,7 @@ def ohms(self, ohms): assert False -# Example 13 +print("Example 13") class MysteriousResistor(Resistor): @property def ohms(self): @@ -184,9 +185,9 @@ def ohms(self, ohms): self._ohms = ohms -# Example 14 +print("Example 14") r7 = MysteriousResistor(10) -r7.current = 0.01 -print(f'Before: {r7.voltage:.2f}') +r7.current = 0.1 +print(f"Before: {r7.voltage:.2f}") r7.ohms -print(f'After: {r7.voltage:.2f}') +print(f"After: {r7.voltage:.2f}") diff --git a/example_code/item_45.py b/example_code/item_059.py similarity index 59% rename from example_code/item_45.py rename to example_code/item_059.py index d627894..267a8f7 100755 --- a/example_code/item_45.py +++ b/example_code/item_059.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") from datetime import datetime, timedelta class Bucket: @@ -56,13 +57,14 @@ def __init__(self, period): self.quota = 0 def __repr__(self): - return f'Bucket(quota={self.quota})' + return f"Bucket(quota={self.quota})" + bucket = Bucket(60) print(bucket) -# Example 2 +print("Example 2") def fill(bucket, amount): now = datetime.now() if (now - bucket.reset_time) > bucket.period_delta: @@ -71,7 +73,7 @@ def fill(bucket, amount): bucket.quota += amount -# Example 3 +print("Example 3") def deduct(bucket, amount): now = datetime.now() if (now - bucket.reset_time) > bucket.period_delta: @@ -82,29 +84,31 @@ def deduct(bucket, amount): return True # Bucket had enough, quota consumed -# Example 4 +print("Example 4") bucket = Bucket(60) fill(bucket, 100) print(bucket) -# Example 5 +print("Example 5") if deduct(bucket, 99): - print('Had 99 quota') + print("Had 99 quota") else: - print('Not enough for 99 quota') + print("Not enough for 99 quota") + print(bucket) -# Example 6 +print("Example 6") if deduct(bucket, 3): - print('Had 3 quota') + print("Had 3 quota") else: - print('Not enough for 3 quota') + print("Not enough for 3 quota") + print(bucket) -# Example 7 +print("Example 7") class NewBucket: def __init__(self, period): self.period_delta = timedelta(seconds=period) @@ -113,17 +117,19 @@ def __init__(self, period): self.quota_consumed = 0 def __repr__(self): - return (f'NewBucket(max_quota={self.max_quota}, ' - f'quota_consumed={self.quota_consumed})') + return ( + f"NewBucket(max_quota={self.max_quota}, " + f"quota_consumed={self.quota_consumed})" + ) -# Example 8 + print("Example 8") @property def quota(self): return self.max_quota - self.quota_consumed -# Example 9 + print("Example 9") @quota.setter def quota(self, amount): delta = self.max_quota - amount @@ -132,31 +138,73 @@ def quota(self, amount): self.quota_consumed = 0 self.max_quota = 0 elif delta < 0: - # Quota being filled for the new period - assert self.quota_consumed == 0 - self.max_quota = amount + # Quota being filled during the period + self.max_quota = amount + self.quota_consumed else: # Quota being consumed during the period - assert self.max_quota >= self.quota_consumed - self.quota_consumed += delta + self.quota_consumed = delta -# Example 10 +print("Example 10") bucket = NewBucket(60) -print('Initial', bucket) +print("Initial", bucket) fill(bucket, 100) -print('Filled', bucket) +print("Filled", bucket) if deduct(bucket, 99): - print('Had 99 quota') + print("Had 99 quota") else: - print('Not enough for 99 quota') + print("Not enough for 99 quota") -print('Now', bucket) +print("Now", bucket) if deduct(bucket, 3): - print('Had 3 quota') + print("Had 3 quota") else: - print('Not enough for 3 quota') + print("Not enough for 3 quota") + +print("Still", bucket) -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_46.py b/example_code/item_060.py similarity index 65% rename from example_code/item_46.py rename to example_code/item_060.py index fd44042..a1bf196 100755 --- a/example_code/item_46.py +++ b/example_code/item_060.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") class Homework: def __init__(self): self._grade = 0 @@ -58,18 +59,17 @@ def grade(self): @grade.setter def grade(self, value): if not (0 <= value <= 100): - raise ValueError( - 'Grade must be between 0 and 100') + raise ValueError("Grade must be between 0 and 100") self._grade = value -# Example 2 +print("Example 2") galileo = Homework() galileo.grade = 95 assert galileo.grade == 95 -# Example 3 +print("Example 3") class Exam: def __init__(self): self._writing_grade = 0 @@ -78,11 +78,10 @@ def __init__(self): @staticmethod def _check_grade(value): if not (0 <= value <= 100): - raise ValueError( - 'Grade must be between 0 and 100') + raise ValueError("Grade must be between 0 and 100") -# Example 4 + print("Example 4") @property def writing_grade(self): return self._writing_grade @@ -109,7 +108,7 @@ def math_grade(self, value): assert galileo.math_grade == 99 -# Example 5 +print("Example 5") class Grade: def __get__(self, instance, instance_type): pass @@ -124,24 +123,24 @@ class Exam: science_grade = Grade() -# Example 6 +print("Example 6") exam = Exam() exam.writing_grade = 40 -# Example 7 -Exam.__dict__['writing_grade'].__set__(exam, 40) +print("Example 7") +Exam.__dict__["writing_grade"].__set__(exam, 40) -# Example 8 +print("Example 8") exam.writing_grade -# Example 9 -Exam.__dict__['writing_grade'].__get__(exam, Exam) +print("Example 9") +Exam.__dict__["writing_grade"].__get__(exam, Exam) -# Example 10 +print("Example 10") class Grade: def __init__(self): self._value = 0 @@ -151,12 +150,11 @@ def __get__(self, instance, instance_type): def __set__(self, instance, value): if not (0 <= value <= 100): - raise ValueError( - 'Grade must be between 0 and 100') + raise ValueError("Grade must be between 0 and 100") self._value = value -# Example 11 +print("Example 11") class Exam: math_grade = Grade() writing_grade = Grade() @@ -165,20 +163,19 @@ class Exam: 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("Writing", first_exam.writing_grade) +print("Science", first_exam.science_grade) -# Example 12 +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(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: +print("Example 13") +class DictGrade: def __init__(self): self._values = {} @@ -189,39 +186,48 @@ def __get__(self, instance, instance_type): def __set__(self, instance, value): if not (0 <= value <= 100): - raise ValueError( - 'Grade must be between 0 and 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() -# Example 14 -from weakref import WeakKeyDictionary +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) -class Grade: - def __init__(self): - self._values = WeakKeyDictionary() +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 self._values.get(instance, 0) + 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') - self._values[instance] = value + raise ValueError("Grade must be between 0 and 100") + setattr(instance, self.internal_name, value) -# Example 15 -class Exam: - math_grade = Grade() - writing_grade = Grade() - science_grade = Grade() +print("Example 16") +class NamedExam: + math_grade = NamedGrade() + writing_grade = NamedGrade() + science_grade = NamedGrade() -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') +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_47.py b/example_code/item_061.py similarity index 60% rename from example_code/item_47.py rename to example_code/item_061.py index 7f93c5f..7619deb 100755 --- a/example_code/item_47.py +++ b/example_code/item_061.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,76 +44,79 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") class LazyRecord: def __init__(self): self.exists = 5 def __getattr__(self, name): - value = f'Value for {name}' + value = f"Value for {name}" setattr(self, name, value) return value -# Example 2 +print("Example 2") data = LazyRecord() -print('Before:', data.__dict__) -print('foo: ', data.foo) -print('After: ', data.__dict__) +print("Before:", data.__dict__) +print("foo: ", data.foo) +print("After: ", data.__dict__) -# Example 3 +print("Example 3") class LoggingLazyRecord(LazyRecord): def __getattr__(self, name): - print(f'* Called __getattr__({name!r}), ' - f'populating instance dictionary') + print( + f"* Called __getattr__({name!r}), " + f"populating instance dictionary" + ) result = super().__getattr__(name) - print(f'* Returning {result!r}') + print(f"* Returning {result!r}") return result data = LoggingLazyRecord() -print('exists: ', data.exists) -print('First foo: ', data.foo) -print('Second foo: ', data.foo) +print("exists: ", data.exists) +print("First foo: ", data.foo) +print("Second foo: ", data.foo) -# Example 4 +print("Example 4") class ValidatingRecord: def __init__(self): self.exists = 5 def __getattribute__(self, name): - print(f'* Called __getattribute__({name!r})') + print(f"* Called __getattribute__({name!r})") try: value = super().__getattribute__(name) - print(f'* Found {name!r}, returning {value!r}') + 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}') + 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("exists: ", data.exists) +print("First foo: ", data.foo) +print("Second foo: ", data.foo) -# Example 5 +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}' + 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 + assert data.foo == "Value for foo" # Test this works data.bad_name except: logging.exception('Expected') @@ -121,21 +124,21 @@ def __getattr__(self, name): assert False -# Example 6 +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("Before: ", data.__dict__) +print("Has first foo: ", hasattr(data, "foo")) +print("After: ", data.__dict__) +print("Has second foo: ", hasattr(data, "foo")) -# Example 7 +print("Example 7") data = ValidatingRecord() # Implements __getattribute__ -print('Has first foo: ', hasattr(data, 'foo')) -print('Has second foo: ', hasattr(data, 'foo')) +print("Has first foo: ", hasattr(data, "foo")) +print("Has second foo: ", hasattr(data, "foo")) -# Example 8 +print("Example 8") class SavingRecord: def __setattr__(self, name, value): # Save some data for the record @@ -143,33 +146,36 @@ def __setattr__(self, name, value): super().__setattr__(name, value) -# Example 9 +print("Example 9") class LoggingSavingRecord(SavingRecord): def __setattr__(self, name, value): - print(f'* Called __setattr__({name!r}, {value!r})') + print(f"* Called __setattr__({name!r}, {value!r})") super().__setattr__(name, value) data = LoggingSavingRecord() -print('Before: ', data.__dict__) +print("Before: ", data.__dict__) data.foo = 5 -print('After: ', data.__dict__) +print("After: ", data.__dict__) data.foo = 7 -print('Finally:', data.__dict__) +print("Finally:", data.__dict__) -# Example 10 +print("Example 10") class BrokenDictionaryRecord: def __init__(self, data): - self._data = {} + self._data = data def __getattribute__(self, name): - print(f'* Called __getattribute__({name!r})') + print(f"* Called __getattribute__({name!r})") return self._data[name] -# Example 11 +print("Example 11") try: - data = BrokenDictionaryRecord({'foo': 3}) + import sys + + sys.setrecursionlimit(50) + data = BrokenDictionaryRecord({"foo": 3}) data.foo except: logging.exception('Expected') @@ -177,7 +183,7 @@ def __getattribute__(self, name): assert False -# Example 12 +print("Example 12") class DictionaryRecord: def __init__(self, data): self._data = data @@ -185,11 +191,11 @@ def __init__(self, data): def __getattribute__(self, name): # Prevent weird interactions with isinstance() used # by example code harness. - if name == '__class__': + if name == "__class__": return DictionaryRecord - print(f'* Called __getattribute__({name!r})') - data_dict = super().__getattribute__('_data') + print(f"* Called __getattribute__({name!r})") + data_dict = super().__getattribute__("_data") return data_dict[name] -data = DictionaryRecord({'foo': 3}) -print('foo: ', data.foo) +data = DictionaryRecord({"foo": 3}) +print("foo: ", data.foo) diff --git a/example_code/item_48.py b/example_code/item_062.py similarity index 74% rename from example_code/item_48.py rename to example_code/item_062.py index 5533175..4a3992a 100755 --- a/example_code/item_48.py +++ b/example_code/item_062.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,15 +44,16 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +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(f"* Running {meta}.__new__ for {name}") + print("Bases:", bases) print = pprint print(class_dict) print = orig_print @@ -71,13 +72,13 @@ def bar(self): pass -# Example 2 +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') + if class_dict["sides"] < 3: + raise ValueError("Polygons need 3+ sides") return type.__new__(meta, name, bases, class_dict) class Polygon(metaclass=ValidatePolygon): @@ -101,30 +102,30 @@ class Nonagon(Polygon): assert Nonagon.interior_angles() == 1260 -# Example 3 +print("Example 3") try: - print('Before class') + print("Before class") class Line(Polygon): - print('Before sides') + print("Before sides") sides = 2 - print('After sides') + print("After sides") - print('After class') + print("After class") except: logging.exception('Expected') else: assert False -# Example 4 +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') + raise ValueError("Polygons need 3+ sides") @classmethod def interior_angles(cls): @@ -136,37 +137,37 @@ class Hexagon(BetterPolygon): assert Hexagon.interior_angles() == 720 -# Example 5 +print("Example 5") try: - print('Before class') + print("Before class") class Point(BetterPolygon): sides = 1 - print('After class') + print("After class") except: logging.exception('Expected') else: assert False -# Example 6 +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') + 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 +print("Example 7") try: class RedPentagon(Filled, Polygon): - color = 'blue' + color = "blue" sides = 5 except: logging.exception('Expected') @@ -174,13 +175,13 @@ class RedPentagon(Filled, Polygon): assert False -# Example 8 +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') + 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): @@ -190,9 +191,9 @@ class Polygon(metaclass=ValidatePolygon): 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') + 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): @@ -200,19 +201,19 @@ class FilledPolygon(Polygon, metaclass=ValidateFilledPolygon): color = None # Must be specified by subclasses -# Example 9 +print("Example 9") class GreenPentagon(FilledPolygon): - color = 'green' + color = "green" sides = 5 greenie = GreenPentagon() assert isinstance(greenie, Polygon) -# Example 10 +print("Example 10") try: class OrangePentagon(FilledPolygon): - color = 'orange' + color = "orange" sides = 5 except: logging.exception('Expected') @@ -220,10 +221,10 @@ class OrangePentagon(FilledPolygon): assert False -# Example 11 +print("Example 11") try: class RedLine(FilledPolygon): - color = 'red' + color = "red" sides = 2 except: logging.exception('Expected') @@ -231,19 +232,19 @@ class RedLine(FilledPolygon): assert False -# Example 12 +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') + if cls.color not in ("red", "green", "blue"): + raise ValueError("Fills need a valid color") -# Example 13 +print("Example 13") class RedTriangle(Filled, BetterPolygon): - color = 'red' + color = "red" sides = 3 ruddy = RedTriangle() @@ -251,53 +252,53 @@ class RedTriangle(Filled, BetterPolygon): assert isinstance(ruddy, BetterPolygon) -# Example 14 +print("Example 14") try: - print('Before class') + print("Before class") class BlueLine(Filled, BetterPolygon): - color = 'blue' + color = "blue" sides = 2 - print('After class') + print("After class") except: logging.exception('Expected') else: assert False -# Example 15 +print("Example 15") try: - print('Before class') + print("Before class") class BeigeSquare(Filled, BetterPolygon): - color = 'beige' + color = "beige" sides = 4 - print('After class') + print("After class") except: logging.exception('Expected') else: assert False -# Example 16 +print("Example 16") class Top: def __init_subclass__(cls): super().__init_subclass__() - print(f'Top for {cls}') + print(f"Top for {cls}") class Left(Top): def __init_subclass__(cls): super().__init_subclass__() - print(f'Left for {cls}') + print(f"Left for {cls}") class Right(Top): def __init_subclass__(cls): super().__init_subclass__() - print(f'Right for {cls}') + print(f"Right for {cls}") class Bottom(Left, Right): def __init_subclass__(cls): super().__init_subclass__() - print(f'Bottom for {cls}') + print(f"Bottom for {cls}") diff --git a/example_code/item_49.py b/example_code/item_063.py similarity index 70% rename from example_code/item_49.py rename to example_code/item_063.py index 7dec89a..56d0ff7 100755 --- a/example_code/item_49.py +++ b/example_code/item_063.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") import json class Serializable: @@ -54,10 +55,10 @@ def __init__(self, *args): self.args = args def serialize(self): - return json.dumps({'args': self.args}) + return json.dumps({"args": self.args}) -# Example 2 +print("Example 2") class Point2D(Serializable): def __init__(self, x, y): super().__init__(x, y) @@ -65,22 +66,22 @@ def __init__(self, x, y): self.y = y def __repr__(self): - return f'Point2D({self.x}, {self.y})' + return f"Point2D({self.x}, {self.y})" point = Point2D(5, 3) -print('Object: ', point) -print('Serialized:', point.serialize()) +print("Object: ", point) +print("Serialized:", point.serialize()) -# Example 3 +print("Example 3") class Deserializable(Serializable): @classmethod def deserialize(cls, json_data): params = json.loads(json_data) - return cls(*params['args']) + return cls(*params["args"]) -# Example 4 +print("Example 4") class BetterPoint2D(Deserializable): def __init__(self, x, y): super().__init__(x, y) @@ -88,47 +89,49 @@ def __init__(self, x, y): self.y = y def __repr__(self): - return f'Point2D({self.x}, {self.y})' + return f"Point2D({self.x}, {self.y})" before = BetterPoint2D(5, 3) -print('Before: ', before) +print("Before: ", before) data = before.serialize() -print('Serialized:', data) +print("Serialized:", data) after = BetterPoint2D.deserialize(data) -print('After: ', after) +print("After: ", after) -# Example 5 +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, - }) + 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})' + args_str = ", ".join(str(x) for x in self.args) + return f"{name}({args_str})" -# Example 6 -registry = {} +print("Example 6") +REGISTRY = {} def register_class(target_class): - registry[target_class.__name__] = 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']) + name = params["class"] + target_class = REGISTRY[name] + return target_class(*params["args"]) -# Example 7 +print("Example 7") class EvenBetterPoint2D(BetterSerializable): def __init__(self, x, y): super().__init__(x, y) @@ -138,16 +141,16 @@ def __init__(self, x, y): register_class(EvenBetterPoint2D) -# Example 8 +print("Example 8") before = EvenBetterPoint2D(5, 3) -print('Before: ', before) +print("Before: ", before) data = before.serialize() -print('Serialized:', data) +print("Serialized:", data) after = deserialize(data) -print('After: ', after) +print("After: ", after) -# Example 9 +print("Example 9") class Point3D(BetterSerializable): def __init__(self, x, y, z): super().__init__(x, y, z) @@ -158,7 +161,7 @@ def __init__(self, x, y, z): # Forgot to call register_class! Whoops! -# Example 10 +print("Example 10") try: point = Point3D(5, 9, -4) data = point.serialize() @@ -169,32 +172,31 @@ def __init__(self, x, y, z): assert False -# Example 11 +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): +class RegisteredSerializable(BetterSerializable, metaclass=Meta): pass -# Example 12 +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) +print("Before: ", before) data = before.serialize() -print('Serialized:', data) -print('After: ', deserialize(data)) +print("Serialized:", data) +print("After: ", deserialize(data)) -# Example 13 +print("Example 13") class BetterRegisteredSerializable(BetterSerializable): def __init_subclass__(cls): super().__init_subclass__() @@ -205,8 +207,10 @@ def __init__(self, magnitude): super().__init__(magnitude) self.magnitude = magnitude + +print("Example 14") before = Vector1D(6) -print('Before: ', before) +print("Before: ", before) data = before.serialize() -print('Serialized:', data) -print('After: ', deserialize(data)) +print("Serialized:", data) +print("After: ", deserialize(data)) diff --git a/example_code/item_50.py b/example_code/item_064.py similarity index 62% rename from example_code/item_50.py rename to example_code/item_064.py index 2a26765..5c2624e 100755 --- a/example_code/item_50.py +++ b/example_code/item_064.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,81 +44,84 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") class Field: - def __init__(self, name): - self.name = name - self.internal_name = '_' + self.name + 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, '') + return getattr(instance, self.internal_name, "") def __set__(self, instance, value): setattr(instance, self.internal_name, value) -# Example 2 +print("Example 3") class Customer: # Class attributes - first_name = Field('first_name') - last_name = Field('last_name') - prefix = Field('prefix') - suffix = Field('suffix') + first_name = Field("first_name") + last_name = Field("last_name") + prefix = Field("prefix") + suffix = Field("suffix") -# Example 3 +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(f"Before: {cust.first_name!r} {cust.__dict__}") +cust.first_name = "Euclid" +print(f"After: {cust.first_name!r} {cust.__dict__}") -# Example 4 +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') + first_name = Field("first_name") + last_name = Field("last_name") + prefix = Field("prefix") + suffix = Field("suffix") -# Example 5 +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.name = key - value.internal_name = '_' + key + value.column_name = key + value.internal_name = "_" + key cls = type.__new__(meta, name, bases, class_dict) return cls -# Example 6 +print("Example 7") class DatabaseRow(metaclass=Meta): pass -# Example 7 +print("Example 8") class Field: def __init__(self): # These will be assigned by the metaclass. - self.name = None + 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, '') + return getattr(instance, self.internal_name, "") def __set__(self, instance, value): setattr(instance, self.internal_name, value) -# Example 8 +print("Example 9") class BetterCustomer(DatabaseRow): first_name = Field() last_name = Field() @@ -126,57 +129,57 @@ class BetterCustomer(DatabaseRow): suffix = Field() -# Example 9 +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(f"Before: {cust.first_name!r} {cust.__dict__}") +cust.first_name = "Euler" +print(f"After: {cust.first_name!r} {cust.__dict__}") -# Example 10 +print("Example 11") try: - class BrokenCustomer: + class BrokenCustomer: # Missing inheritance first_name = Field() last_name = Field() prefix = Field() suffix = Field() cust = BrokenCustomer() - cust.first_name = 'Mersenne' + cust.first_name = "Mersenne" except: logging.exception('Expected') else: assert False -# Example 11 +print("Example 12") class Field: def __init__(self): - self.name = None + self.column_name = None self.internal_name = None - def __set_name__(self, owner, name): + def __set_name__(self, owner, column_name): # Called on class creation for each descriptor - self.name = name - self.internal_name = '_' + name + 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, '') + return getattr(instance, self.internal_name, "") def __set__(self, instance, value): setattr(instance, self.internal_name, value) -# Example 12 -class FixedCustomer: +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__}') +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..d7175f9 --- /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_51.py b/example_code/item_066.py similarity index 61% rename from example_code/item_51.py rename to example_code/item_066.py index 0beacd7..1d869b8 100755 --- a/example_code/item_51.py +++ b/example_code/item_066.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,17 +44,20 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") from functools import wraps def trace_func(func): - if hasattr(func, 'tracing'): # Only decorate once + 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) @@ -63,18 +66,21 @@ def wrapper(*args, **kwargs): result = e raise finally: - print(f'{func.__name__}({args!r}, {kwargs!r}) -> ' - f'{result!r}') + print( + f"{func.__name__}" + f"({args_repr}, {kwargs_repr}) -> " + f"{result!r}" + ) wrapper.tracing = True return wrapper -# Example 2 +print("Example 2") class TraceDict(dict): @trace_func def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + return super().__init__(*args, **kwargs) @trace_func def __setitem__(self, *args, **kwargs): @@ -85,58 +91,70 @@ def __getitem__(self, *args, **kwargs): return super().__getitem__(*args, **kwargs) -# Example 3 -trace_dict = TraceDict([('hi', 1)]) -trace_dict['there'] = 2 -trace_dict['hi'] +print("Example 3") +trace_dict = TraceDict([("hi", 1)]) +trace_dict["there"] = 2 +trace_dict["hi"] try: - trace_dict['does not exist'] + trace_dict["does not exist"] except KeyError: pass # Expected else: assert False -# Example 4 +print("Example 4") import types -trace_types = ( +TRACE_TYPES = ( types.MethodType, types.FunctionType, types.BuiltinFunctionType, types.BuiltinMethodType, types.MethodDescriptorType, - types.ClassMethodDescriptorType) + 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 isinstance(value, trace_types): - wrapped = trace_func(value) - setattr(klass, key, wrapped) + if not isinstance(value, TRACE_TYPES): + continue + + wrapped = trace_func(value) + setattr(klass, key, wrapped) return klass -# Example 5 +print("Example 5") class TraceDict(dict, metaclass=TraceMeta): pass -trace_dict = TraceDict([('hi', 1)]) -trace_dict['there'] = 2 -trace_dict['hi'] +trace_dict = TraceDict([("hi", 1)]) +trace_dict["there"] = 2 +trace_dict["hi"] try: - trace_dict['does not exist'] + trace_dict["does not exist"] except KeyError: pass # Expected else: assert False -# Example 6 +print("Example 6") try: class OtherMeta(type): pass @@ -144,7 +162,7 @@ class OtherMeta(type): class SimpleDict(dict, metaclass=OtherMeta): pass - class TraceDict(SimpleDict, metaclass=TraceMeta): + class ChildTraceDict(SimpleDict, metaclass=TraceMeta): pass except: logging.exception('Expected') @@ -152,42 +170,48 @@ class TraceDict(SimpleDict, metaclass=TraceMeta): assert False -# Example 7 +print("Example 7") class TraceMeta(type): def __new__(meta, name, bases, class_dict): - klass = type.__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 isinstance(value, trace_types): - wrapped = trace_func(value) - setattr(klass, key, wrapped) + 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 TraceDict(SimpleDict, metaclass=TraceMeta): +class ChildTraceDict(SimpleDict, metaclass=TraceMeta): pass -trace_dict = TraceDict([('hi', 1)]) -trace_dict['there'] = 2 -trace_dict['hi'] +trace_dict = ChildTraceDict([("hi", 1)]) +trace_dict["there"] = 2 +trace_dict["hi"] try: - trace_dict['does not exist'] + trace_dict["does not exist"] except KeyError: pass # Expected else: assert False -# Example 8 +print("Example 8") def my_class_decorator(klass): - klass.extra_param = 'hello' + klass.extra_param = "hello" return klass @my_class_decorator @@ -198,45 +222,51 @@ class MyClass: print(MyClass.extra_param) -# Example 9 +print("Example 9") def trace(klass): for key in dir(klass): + if key in IGNORE_METHODS: + continue + value = getattr(klass, key) - if isinstance(value, trace_types): - wrapped = trace_func(value) - setattr(klass, key, wrapped) + if not isinstance(value, TRACE_TYPES): + continue + + wrapped = trace_func(value) + setattr(klass, key, wrapped) + return klass -# Example 10 +print("Example 10") @trace -class TraceDict(dict): +class DecoratedTraceDict(dict): pass -trace_dict = TraceDict([('hi', 1)]) -trace_dict['there'] = 2 -trace_dict['hi'] +trace_dict = DecoratedTraceDict([("hi", 1)]) +trace_dict["there"] = 2 +trace_dict["hi"] try: - trace_dict['does not exist'] + trace_dict["does not exist"] except KeyError: pass # Expected else: assert False -# Example 11 +print("Example 11") class OtherMeta(type): pass @trace -class TraceDict(dict, metaclass=OtherMeta): +class HasMetaTraceDict(dict, metaclass=OtherMeta): pass -trace_dict = TraceDict([('hi', 1)]) -trace_dict['there'] = 2 -trace_dict['hi'] +trace_dict = HasMetaTraceDict([("hi", 1)]) +trace_dict["there"] = 2 +trace_dict["hi"] try: - trace_dict['does not exist'] + trace_dict["does not exist"] except KeyError: pass # Expected else: diff --git a/example_code/item_52.py b/example_code/item_067.py similarity index 79% rename from example_code/item_52.py rename to example_code/item_067.py index d112055..73b94d4 100755 --- a/example_code/item_52.py +++ b/example_code/item_067.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,79 +44,84 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +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!'], + ["echo", "Hello from the child!"], capture_output=True, # Enable this line to make this example work on Windows # shell=True, - encoding='utf-8') + encoding="utf-8", +) result.check_returncode() # No exception means it exited cleanly print(result.stdout) -# Example 2 +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']) +proc = subprocess.Popen(["sleep", "1"]) while proc.poll() is None: - print('Working...') + print("Working...") # Some time-consuming work here import time + time.sleep(0.3) -print('Exit status', proc.poll()) +print("Exit status", proc.poll()) -# Example 3 +print("Example 3") import time -start = time.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']) + proc = subprocess.Popen(["sleep", "1"]) sleep_procs.append(proc) -# Example 4 +print("Example 4") for proc in sleep_procs: proc.communicate() -end = time.time() +end = time.perf_counter() delta = end - start -print(f'Finished in {delta:.3} seconds') +print(f"Finished in {delta:.3} seconds") -# Example 5 +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' + 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) + stdout=subprocess.PIPE, + ) proc.stdin.write(data) proc.stdin.flush() # Ensure that the child gets input return proc -# Example 6 +print("Example 6") procs = [] for _ in range(3): data = os.urandom(10) @@ -124,21 +129,22 @@ def run_encrypt(data): procs.append(proc) -# Example 7 +print("Example 7") for proc in procs: out, _ = proc.communicate() print(out[-10:]) -# Example 8 +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) + stdout=subprocess.PIPE, + ) -# Example 9 +print("Example 9") encrypt_procs = [] hash_procs = [] for _ in range(3): @@ -158,7 +164,7 @@ def run_hash(input_stdin): encrypt_proc.stdout = None -# Example 10 +print("Example 10") for proc in encrypt_procs: proc.communicate() assert proc.returncode == 0 @@ -169,14 +175,14 @@ def run_hash(input_stdin): assert proc.returncode == 0 -# Example 11 +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']) +proc = subprocess.Popen(["sleep", "10"]) try: proc.communicate(timeout=0.1) except subprocess.TimeoutExpired: proc.terminate() proc.wait() -print('Exit status', proc.poll()) +print("Exit status", proc.poll()) diff --git a/example_code/item_53.py b/example_code/item_068.py similarity index 72% rename from example_code/item_53.py rename to example_code/item_068.py index fdab48f..77d3641 100755 --- a/example_code/item_53.py +++ b/example_code/item_068.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,30 +44,34 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") def factorize(number): for i in range(1, number + 1): if number % i == 0: yield i -# Example 2 +print("Example 2") import time -numbers = [2139079, 1214759, 1516637, 1852285] -start = time.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.time() +end = time.perf_counter() delta = end - start -print(f'Took {delta:.3f} seconds') +print(f"Took {delta:.3f} seconds") -# Example 3 +print("Example 3") from threading import Thread class FactorizeThread(Thread): @@ -79,8 +83,8 @@ def run(self): self.factors = list(factorize(self.number)) -# Example 4 -start = time.time() +print("Example 4") +start = time.perf_counter() threads = [] for number in numbers: @@ -89,16 +93,16 @@ def run(self): threads.append(thread) -# Example 5 +print("Example 5") for thread in threads: thread.join() -end = time.time() +end = time.perf_counter() delta = end - start -print(f'Took {delta:.3f} seconds') +print(f"Took {delta:.3f} seconds") -# Example 6 +print("Example 6") import select import socket @@ -106,19 +110,19 @@ def slow_systemcall(): select.select([socket.socket()], [], [], 0.1) -# Example 7 -start = time.time() +print("Example 7") +start = time.perf_counter() for _ in range(5): slow_systemcall() -end = time.time() +end = time.perf_counter() delta = end - start -print(f'Took {delta:.3f} seconds') +print(f"Took {delta:.3f} seconds") -# Example 8 -start = time.time() +print("Example 8") +start = time.perf_counter() threads = [] for _ in range(5): @@ -127,7 +131,7 @@ def slow_systemcall(): threads.append(thread) -# Example 9 +print("Example 9") def compute_helicopter_location(index): pass @@ -137,6 +141,6 @@ def compute_helicopter_location(index): for thread in threads: thread.join() -end = time.time() +end = time.perf_counter() delta = end - start -print(f'Took {delta:.3f} seconds') +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_55.py b/example_code/item_070.py similarity index 53% rename from example_code/item_55.py rename to example_code/item_070.py index 86696b2..351a99f 100755 --- a/example_code/item_55.py +++ b/example_code/item_070.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") def download(item): return item @@ -57,7 +58,7 @@ def upload(item): return item -# Example 2 +print("Example 2") from collections import deque from threading import Lock @@ -67,19 +68,19 @@ def __init__(self): self.lock = Lock() -# Example 3 + print("Example 3") def put(self, item): with self.lock: self.items.append(item) -# Example 4 + print("Example 4") def get(self): with self.lock: return self.items.popleft() -# Example 5 +print("Example 5") from threading import Thread import time @@ -93,7 +94,7 @@ def __init__(self, func, in_queue, out_queue): self.work_done = 0 -# Example 6 + print("Example 6") def run(self): while True: self.polled_count += 1 @@ -102,7 +103,8 @@ def run(self): except IndexError: time.sleep(0.01) # No work to do except AttributeError: - # The magic exit signal + # 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) @@ -110,7 +112,7 @@ def run(self): self.work_done += 1 -# Example 7 +print("Example 7") download_queue = MyQueue() resize_queue = MyQueue() upload_queue = MyQueue() @@ -122,7 +124,7 @@ def run(self): ] -# Example 8 +print("Example 8") for thread in threads: thread.start() @@ -130,7 +132,7 @@ def run(self): download_queue.put(object()) -# Example 9 +print("Example 9") while len(done_queue.items) < 1000: # Do something useful while waiting time.sleep(0.1) @@ -141,103 +143,111 @@ def run(self): thread.join() -# Example 10 +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(f"Processed {processed} items after " f"polling {polled} times") -# Example 11 +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') + 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') +print("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 +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') + 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') +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() -# Example 15 +print("Example 15") in_queue = Queue() def consumer(): - print('Consumer waiting') - work = in_queue.get() # Done second - print('Consumer working') + print("Consumer waiting") + work = in_queue.get() # Runs second + print("Consumer working") # Doing work - print('Consumer done') - in_queue.task_done() # Done third + print("Consumer done") + in_queue.task_done() # Runs 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') +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() -# Example 17 -class ClosableQueue(Queue): - SENTINEL = object() +print("Example 17") +from queue import ShutDown - def close(self): - self.put(self.SENTINEL) +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() -# 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() +thread = Thread(target=consumer) +my_queue2.put(1) +my_queue2.put(2) +my_queue2.put(3) +my_queue2.shutdown() +thread.start() -# Example 19 +my_queue2.join() +thread.join() +print("Done") + + +print("Example 18") class StoppableWorker(Thread): def __init__(self, func, in_queue, out_queue): super().__init__() @@ -246,80 +256,127 @@ def __init__(self, func, in_queue, out_queue): self.out_queue = out_queue def run(self): - for item in self.in_queue: - result = self.func(item) - self.out_queue.put(result) + 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() -# 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() + +print("Example 20") for _ in range(1000): download_queue.put(object()) -download_queue.close() - -# Example 22 +print("Example 21") +download_queue.shutdown() download_queue.join() -resize_queue.close() + +resize_queue.shutdown() resize_queue.join() -upload_queue.close() + +upload_queue.shutdown() upload_queue.join() -print(done_queue.qsize(), 'items finished') + + +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") -# Example 23 + +print("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() +def drain_queue(input_queue): + input_queue.shutdown() - closable_queue.join() + counter = 0 - for thread in threads: - thread.join() + while True: + try: + item = input_queue.get() + except ShutDown: + break + else: + input_queue.task_done() + counter += 1 + input_queue.join() -# Example 24 -download_queue = ClosableQueue() -resize_queue = ClosableQueue() -upload_queue = ClosableQueue() -done_queue = ClosableQueue() + return counter -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): +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()) -stop_threads(download_queue, download_threads) -stop_threads(resize_queue, resize_threads) -stop_threads(upload_queue, upload_threads) +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(done_queue.qsize(), 'items finished') +print(counter, "items finished") diff --git a/example_code/item_56.py b/example_code/item_071.py similarity index 78% rename from example_code/item_56.py rename to example_code/item_071.py index b01e83b..6ff868d 100755 --- a/example_code/item_56.py +++ b/example_code/item_071.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,14 +44,15 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 -ALIVE = '*' -EMPTY = '-' +print("Example 1") +ALIVE = "*" +EMPTY = "-" -# Example 2 +print("Example 2") class Grid: def __init__(self, height, width): self.height = height @@ -67,15 +68,15 @@ def set(self, y, x, state): self.rows[y % self.height][x % self.width] = state def __str__(self): - output = '' + output = "" for row in self.rows: for cell in row: output += cell - output += '\n' + output += "\n" return output -# Example 3 +print("Example 3") grid = Grid(5, 9) grid.set(0, 3, ALIVE) grid.set(1, 4, ALIVE) @@ -85,16 +86,16 @@ def __str__(self): 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 +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: @@ -102,6 +103,7 @@ def count_neighbors(y, x, get): count += 1 return count + alive = {(9, 5), (9, 6)} seen = set() @@ -114,13 +116,19 @@ def fake_get(y, x): assert count == 2 expected_seen = { - (9, 5), (9, 6), (10, 6), (11, 6), - (11, 5), (11, 4), (10, 4), (9, 4) + (9, 5), + (9, 6), + (10, 6), + (11, 6), + (11, 5), + (11, 4), + (10, 4), + (9, 4), } assert seen == expected_seen -# Example 5 +print("Example 5") def game_logic(state, neighbors): if state == ALIVE: if neighbors < 2: @@ -144,12 +152,13 @@ def game_logic(state, neighbors): 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) +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(y, x, next_state) + set_cell(y, x, next_state) + alive = {(10, 5), (9, 5), (9, 6)} new_state = None @@ -176,7 +185,7 @@ def fake_set(y, x, state): assert new_state == ALIVE -# Example 7 +print("Example 7") def simulate(grid): next_grid = Grid(grid.height, grid.width) for y in range(grid.height): @@ -185,7 +194,7 @@ def simulate(grid): return next_grid -# Example 8 +print("Example 8") class ColumnPrinter: def __init__(self): self.columns = [] @@ -196,23 +205,23 @@ def append(self, data): def __str__(self): row_count = 1 for data in self.columns: - row_count = max( - row_count, len(data.splitlines()) + 1) + row_count = max(row_count, len(data.splitlines()) + 1) - rows = [''] * row_count + 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) + padding = " " * (len(line) // 2) rows[j] += padding + str(i) + padding else: rows[j] += line if (i + 1) < len(self.columns): - rows[j] += ' | ' + rows[j] += " | " + + return "\n".join(rows) - return '\n'.join(rows) columns = ColumnPrinter() for i in range(5): @@ -222,7 +231,7 @@ def __str__(self): print(columns) -# Example 9 +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_57.py b/example_code/item_072.py similarity index 73% rename from example_code/item_57.py rename to example_code/item_072.py index b236a72..a0e57bb 100755 --- a/example_code/item_57.py +++ b/example_code/item_072.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,13 +44,14 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") from threading import Lock -ALIVE = '*' -EMPTY = '-' +ALIVE = "*" +EMPTY = "-" class Grid: def __init__(self, height, width): @@ -67,13 +68,14 @@ def set(self, y, x, state): self.rows[y % self.height][x % self.width] = state def __str__(self): - output = '' + output = "" for row in self.rows: for cell in row: output += cell - output += '\n' + output += "\n" return output + class LockingGrid(Grid): def __init__(self, height, width): super().__init__(height, width) @@ -92,18 +94,18 @@ def set(self, y, x, state): return super().set(y, x, state) -# Example 2 +print("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 +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: @@ -112,25 +114,28 @@ def count_neighbors(y, x, get): 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 # Die: Too few + return EMPTY elif neighbors > 3: - return EMPTY # Die: Too many + return EMPTY else: if neighbors == 3: - return ALIVE # Regenerate + return ALIVE return state -def step_cell(y, x, get, set): - state = get(y, x) - neighbors = count_neighbors(y, x, get) +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(y, x, next_state) + set_cell(y, x, next_state) def simulate_threaded(grid): next_grid = LockingGrid(grid.height, grid.width) @@ -140,16 +145,16 @@ def simulate_threaded(grid): 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 + thread.start() # Fan-out threads.append(thread) for thread in threads: - thread.join() # Fan in + thread.join() # Fan-in return next_grid -# Example 3 +print("Example 3") class ColumnPrinter: def __init__(self): self.columns = [] @@ -160,25 +165,25 @@ def append(self, data): def __str__(self): row_count = 1 for data in self.columns: - row_count = max( - row_count, len(data.splitlines()) + 1) + row_count = max(row_count, len(data.splitlines()) + 1) - rows = [''] * row_count + 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) + padding = " " * (len(line) // 2) rows[j] += padding + str(i) + padding else: rows[j] += line if (i + 1) < len(self.columns): - rows[j] += ' | ' + rows[j] += " | " + + return "\n".join(rows) - return '\n'.join(rows) -grid = LockingGrid(5, 9) # Changed +grid = LockingGrid(5, 9) # Changed grid.set(0, 3, ALIVE) grid.set(1, 4, ALIVE) grid.set(2, 2, ALIVE) @@ -193,12 +198,12 @@ def __str__(self): print(columns) -# Example 4 +print("Example 4") def game_logic(state, neighbors): - raise OSError('Problem with I/O') + raise OSError("Problem with I/O") -# Example 5 +print("Example 5") import contextlib import io diff --git a/example_code/item_58.py b/example_code/item_073.py similarity index 65% rename from example_code/item_58.py rename to example_code/item_073.py index e2b765d..8e23346 100755 --- a/example_code/item_58.py +++ b/example_code/item_073.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,45 +44,40 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") from queue import Queue -class ClosableQueue(Queue): - SENTINEL = object() +in_queue = Queue() +out_queue = Queue() - 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() +print("Example 2") +from threading import Thread +from queue import ShutDown -# Example 2 -from threading import Thread class StoppableWorker(Thread): - def __init__(self, func, in_queue, out_queue, **kwargs): - super().__init__(**kwargs) + 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): - for item in self.in_queue: - result = self.func(item) - self.out_queue.put(result) + 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: @@ -91,12 +86,12 @@ def game_logic(state, neighbors): def game_logic(state, neighbors): if state == ALIVE: if neighbors < 2: - return EMPTY # Die: Too few + return EMPTY elif neighbors > 3: - return EMPTY # Die: Too many + return EMPTY else: if neighbors == 3: - return ALIVE # Regenerate + return ALIVE return state def game_logic_thread(item): @@ -110,15 +105,14 @@ def game_logic_thread(item): # Start the threads upfront threads = [] for _ in range(5): - thread = StoppableWorker( - game_logic_thread, in_queue, out_queue) + thread = StoppableWorker(game_logic_thread, in_queue, out_queue) thread.start() threads.append(thread) -# Example 3 -ALIVE = '*' -EMPTY = '-' +print("Example 3") +ALIVE = "*" +EMPTY = "-" class SimulationError(Exception): pass @@ -138,22 +132,23 @@ def set(self, y, x, state): self.rows[y % self.height][x % self.width] = state def __str__(self): - output = '' + output = "" for row in self.rows: for cell in row: output += cell - output += '\n' + 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 + +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: @@ -166,13 +161,14 @@ def simulate_pipeline(grid, in_queue, out_queue): 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.put((y, x, state, neighbors)) # Fan-out in_queue.join() - out_queue.close() + item_count = out_queue.qsize() next_grid = Grid(grid.height, grid.width) - for item in out_queue: # Fan in + 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 @@ -181,10 +177,10 @@ def simulate_pipeline(grid, in_queue, out_queue): return next_grid -# Example 4 +print("Example 4") try: def game_logic(state, neighbors): - raise OSError('Problem with I/O in game_logic') + raise OSError("Problem with I/O in game_logic") simulate_pipeline(Grid(1, 1), in_queue, out_queue) except: @@ -193,21 +189,17 @@ def game_logic(state, neighbors): assert False -# Example 5 -# Clear the sentinel object from the out queue -for _ in out_queue: - pass - +print("Example 5") # Restore the working version of this function def game_logic(state, neighbors): if state == ALIVE: if neighbors < 2: - return EMPTY # Die: Too few + return EMPTY elif neighbors > 3: - return EMPTY # Die: Too many + return EMPTY else: if neighbors == 3: - return ALIVE # Regenerate + return ALIVE return state class ColumnPrinter: @@ -220,23 +212,23 @@ def append(self, data): def __str__(self): row_count = 1 for data in self.columns: - row_count = max( - row_count, len(data.splitlines()) + 1) + row_count = max(row_count, len(data.splitlines()) + 1) - rows = [''] * row_count + 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) + padding = " " * (len(line) // 2) rows[j] += padding + str(i) + padding else: rows[j] += line if (i + 1) < len(self.columns): - rows[j] += ' | ' + rows[j] += " | " + + return "\n".join(rows) - return '\n'.join(rows) grid = Grid(5, 9) grid.set(0, 3, ALIVE) @@ -252,28 +244,29 @@ def __str__(self): print(columns) -for thread in threads: - in_queue.close() +in_queue.shutdown() +in_queue.join() + for thread in threads: thread.join() -# Example 6 -def count_neighbors(y, x, get): +print("Example 6") +def count_neighbors(y, x, get_cell): # 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 +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: @@ -282,9 +275,9 @@ def count_neighbors(y, x, get): return count def count_neighbors_thread(item): - y, x, state, get = item + y, x, state, get_cell = item try: - neighbors = count_neighbors(y, x, get) + neighbors = count_neighbors(y, x, get_cell) except Exception as e: neighbors = e return (y, x, state, neighbors) @@ -320,42 +313,43 @@ def set(self, y, x, state): return super().set(y, x, state) -# Example 8 -in_queue = ClosableQueue() -logic_queue = ClosableQueue() -out_queue = ClosableQueue() +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) + 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) + 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): +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.put(item) # Fan-out in_queue.join() - logic_queue.join() # Pipeline sequencing - out_queue.close() + logic_queue.join() # Pipeline sequencing + item_count = out_queue.qsize() next_grid = LockingGrid(grid.height, grid.width) - for item in out_queue: # Fan in - y, x, next_state = item + 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) @@ -363,7 +357,7 @@ def simulate_phased_pipeline( return next_grid -# Example 10 +print("Example 10") grid = LockingGrid(5, 9) grid.set(0, 3, ALIVE) grid.set(1, 4, ALIVE) @@ -375,42 +369,44 @@ def simulate_phased_pipeline( for i in range(5): columns.append(str(grid)) grid = simulate_phased_pipeline( - grid, in_queue, logic_queue, out_queue) + grid, in_queue, logic_queue, out_queue + ) print(columns) -for thread in threads: - in_queue.close() -for thread in threads: - logic_queue.close() +in_queue.shutdown() +in_queue.join() + +logic_queue.shutdown() +logic_queue.join() + for thread in threads: thread.join() -# Example 11 +print("Example 11") # Make sure exception propagation works as expected def count_neighbors(*args): - raise OSError('Problem with I/O in count_neighbors') + raise OSError("Problem with I/O in count_neighbors") -in_queue = ClosableQueue() -logic_queue = ClosableQueue() -out_queue = ClosableQueue() +in_queue = Queue() +logic_queue = Queue() +out_queue = Queue() threads = [ StoppableWorker( - count_neighbors_thread, in_queue, logic_queue, - daemon=True), + count_neighbors_thread, in_queue, logic_queue, daemon=True + ), StoppableWorker( - game_logic_thread, logic_queue, out_queue, - daemon=True), + 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) + simulate_phased_pipeline(grid, in_queue, logic_queue, out_queue) except SimulationError: pass # Expected else: diff --git a/example_code/item_59.py b/example_code/item_074.py similarity index 76% rename from example_code/item_59.py rename to example_code/item_074.py index bd4ca10..ec48443 100755 --- a/example_code/item_59.py +++ b/example_code/item_074.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,11 +44,12 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 -ALIVE = '*' -EMPTY = '-' +print("Example 1") +ALIVE = "*" +EMPTY = "-" class Grid: def __init__(self, height, width): @@ -65,13 +66,14 @@ def set(self, y, x, state): self.rows[y % self.height][x % self.width] = state def __str__(self): - output = '' + output = "" for row in self.rows: for cell in row: output += cell - output += '\n' + output += "\n" return output + from threading import Lock class LockingGrid(Grid): @@ -91,15 +93,16 @@ 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 + +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: @@ -114,22 +117,22 @@ def game_logic(state, neighbors): def game_logic(state, neighbors): if state == ALIVE: if neighbors < 2: - return EMPTY # Die: Too few + return EMPTY elif neighbors > 3: - return EMPTY # Die: Too many + return EMPTY else: if neighbors == 3: - return ALIVE # Regenerate + return ALIVE return state -def step_cell(y, x, get, set): - state = get(y, x) - neighbors = count_neighbors(y, x, get) +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(y, x, next_state) + set_cell(y, x, next_state) -# Example 2 +print("Example 2") from concurrent.futures import ThreadPoolExecutor def simulate_pool(pool, grid): @@ -139,16 +142,16 @@ def simulate_pool(pool, grid): 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 + future = pool.submit(step_cell, *args) # Fan-out futures.append(future) for future in futures: - future.result() # Fan in + future.result() # Fan-in return next_grid -# Example 3 +print("Example 3") class ColumnPrinter: def __init__(self): self.columns = [] @@ -159,23 +162,23 @@ def append(self, data): def __str__(self): row_count = 1 for data in self.columns: - row_count = max( - row_count, len(data.splitlines()) + 1) + row_count = max(row_count, len(data.splitlines()) + 1) - rows = [''] * row_count + 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) + padding = " " * (len(line) // 2) rows[j] += padding + str(i) + padding else: rows[j] += line if (i + 1) < len(self.columns): - rows[j] += ' | ' + rows[j] += " | " + + return "\n".join(rows) - return '\n'.join(rows) grid = LockingGrid(5, 9) grid.set(0, 3, ALIVE) @@ -193,10 +196,10 @@ def __str__(self): print(columns) -# Example 4 +print("Example 4") try: def game_logic(state, neighbors): - raise OSError('Problem with I/O') + raise OSError("Problem with I/O") with ThreadPoolExecutor(max_workers=10) as pool: task = pool.submit(game_logic, ALIVE, 3) diff --git a/example_code/item_60.py b/example_code/item_075.py similarity index 65% rename from example_code/item_60.py rename to example_code/item_075.py index 5e1c0b4..f92fbb6 100755 --- a/example_code/item_60.py +++ b/example_code/item_075.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,11 +44,12 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 -ALIVE = '*' -EMPTY = '-' +print("Example 1") +ALIVE = "*" +EMPTY = "-" class Grid: def __init__(self, height, width): @@ -65,22 +66,23 @@ def set(self, y, x, state): self.rows[y % self.height][x % self.width] = state def __str__(self): - output = '' + output = "" for row in self.rows: for cell in row: output += cell - output += '\n' + 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 + +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: @@ -92,27 +94,28 @@ 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 + return EMPTY elif neighbors > 3: - return EMPTY # Die: Too many + return EMPTY else: if neighbors == 3: - return ALIVE # Regenerate + return ALIVE return state -# Example 2 -async def step_cell(y, x, get, set): - state = get(y, x) - neighbors = count_neighbors(y, x, get) +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(y, x, next_state) + set_cell(y, x, next_state) -# Example 3 +print("Example 3") import asyncio async def simulate(grid): @@ -121,16 +124,15 @@ async def simulate(grid): 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 + task = step_cell(y, x, grid.get, next_grid.set) # Fan-out tasks.append(task) - await asyncio.gather(*tasks) # Fan in + await asyncio.gather(*tasks) # Fan-in return next_grid -# Example 4 +print("Example 4") class ColumnPrinter: def __init__(self): self.columns = [] @@ -141,23 +143,23 @@ def append(self, data): def __str__(self): row_count = 1 for data in self.columns: - row_count = max( - row_count, len(data.splitlines()) + 1) + row_count = max(row_count, len(data.splitlines()) + 1) - rows = [''] * row_count + 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) + padding = " " * (len(line) // 2) rows[j] += padding + str(i) + padding else: rows[j] += line if (i + 1) < len(self.columns): - rows[j] += ' | ' + rows[j] += " | " + + return "\n".join(rows) - return '\n'.join(rows) logging.getLogger().setLevel(logging.ERROR) @@ -171,39 +173,23 @@ def __str__(self): columns = ColumnPrinter() for i in range(5): columns.append(str(grid)) - grid = asyncio.run(simulate(grid)) # Run the event loop + 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 +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: @@ -211,21 +197,21 @@ async def count_neighbors(y, x, get): count += 1 return count -async def step_cell(y, x, get, set): - state = get(y, x) - neighbors = await count_neighbors(y, x, get) +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(y, x, next_state) + set_cell(y, x, next_state) async def game_logic(state, neighbors): if state == ALIVE: if neighbors < 2: - return EMPTY # Die: Too few + return EMPTY elif neighbors > 3: - return EMPTY # Die: Too many + return EMPTY else: if neighbors == 3: - return ALIVE # Regenerate + return ALIVE return state logging.getLogger().setLevel(logging.ERROR) diff --git a/example_code/item_076.py b/example_code/item_076.py new file mode 100755 index 0000000..46a1693 --- /dev/null +++ b/example_code/item_076.py @@ -0,0 +1,498 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 +import time + +@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_62.py b/example_code/item_077.py similarity index 75% rename from example_code/item_62.py rename to example_code/item_077.py index d94a1f4..66867e1 100755 --- a/example_code/item_62.py +++ b/example_code/item_077.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") class NoNewData(Exception): pass @@ -62,7 +63,7 @@ def readline(handle): return handle.readline() -# Example 2 +print("Example 2") import time def tail_file(handle, interval, write_func): @@ -75,12 +76,13 @@ def tail_file(handle, interval, write_func): write_func(line) -# Example 3 +print("Example 3") from threading import Lock, Thread def run_threads(handles, interval, output_path): - with open(output_path, 'wb') as output: + with open(output_path, "wb") as output: lock = Lock() + def write(data): with lock: output.write(data) @@ -96,7 +98,7 @@ def write(data): thread.join() -# Example 4 +print("Example 4") # This is all code to simulate the writers to the handles import collections import os @@ -105,11 +107,10 @@ def write(data): from tempfile import TemporaryDirectory def write_random_data(path, write_count, interval): - with open(path, 'wb') as f: + 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) + letters = random.choices(string.ascii_lowercase, k=10) data = f'{path}-{i:02}-{"".join(letters)}\n' f.write(data.encode()) f.flush() @@ -118,7 +119,7 @@ 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'): + with open(path, "w"): # Make sure the file at this path will exist when # the reading thread tries to poll it. pass @@ -139,19 +140,19 @@ def setup(): handles = [] for path in input_paths: - handle = open(path, 'rb') + handle = open(path, "rb") handles.append(handle) Thread(target=close_all, args=(handles,)).start() - output_path = os.path.join(tmpdir.name, 'merged') + output_path = os.path.join(tmpdir.name, "merged") return tmpdir, input_paths, handles, output_path -# Example 5 +print("Example 5") def confirm_merge(input_paths, output_path): found = collections.defaultdict(list) - with open(output_path, 'rb') as f: + with open(output_path, "rb") as f: for line in f: for path in input_paths: if line.find(path.encode()) == 0: @@ -159,13 +160,14 @@ def confirm_merge(input_paths, output_path): expected = collections.defaultdict(list) for path in input_paths: - with open(path, 'rb') as f: + 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}' + assert ( + expected_lines == found_lines + ), f"{expected_lines!r} == {found_lines!r}" input_paths = ... handles = ... @@ -180,39 +182,44 @@ def confirm_merge(input_paths, output_path): tmpdir.cleanup() -# Example 6 +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 - +# 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: + output = await loop.run_in_executor(None, open, output_path, "wb") + try: + async def write_async(data): - output.write(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 = asyncio.run_coroutine_threadsafe(coro, loop) future.result() tasks = [] for handle in handles: task = loop.run_in_executor( - None, tail_file, handle, interval, write) + None, tail_file, handle, interval, write + ) tasks.append(task) await asyncio.gather(*tasks) + finally: + await loop.run_in_executor(None, output.close) -# Example 7 +print("Example 7") input_paths = ... handles = ... output_path = ... @@ -226,36 +233,39 @@ def write(data): tmpdir.cleanup() -# Example 8 +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) + line = await loop.run_in_executor(None, readline, handle) except NoNewData: await asyncio.sleep(interval) else: await write_func(line) -# Example 9 +print("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) + loop = asyncio.get_event_loop() - tasks = [] - for handle in handles: - coro = tail_async(handle, interval, write_async) - task = asyncio.create_task(coro) - tasks.append(task) + output = await loop.run_in_executor(None, open, output_path, "wb") + try: - await asyncio.gather(*tasks) + 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) -# Example 10 +print("Example 10") input_paths = ... handles = ... output_path = ... @@ -269,19 +279,19 @@ async def write_async(data): tmpdir.cleanup() -# Example 11 +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): - write_func(data) + await loop.run_in_executor(None, write_func, data) coro = tail_async(handle, interval, write_async) loop.run_until_complete(coro) -# Example 12 +print("Example 12") input_paths = ... handles = ... output_path = ... diff --git a/example_code/item_63.py b/example_code/item_078.py similarity index 74% rename from example_code/item_63.py rename to example_code/item_078.py index 5a56e96..43d67ba 100755 --- a/example_code/item_63.py +++ b/example_code/item_078.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,33 +44,45 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("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: + loop = asyncio.get_event_loop() + + output = await loop.run_in_executor(None, open, output_path, "wb") + try: + async def write_async(data): - output.write(data) + await loop.run_in_executor(None, output.write, data) - tasks = [] - for handle in handles: - coro = tail_async(handle, interval, write_async) - task = asyncio.create_task(coro) - tasks.append(task) + 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) - await asyncio.gather(*tasks) +print("Example 2") +async def run_tasks_simpler(handles, interval, output_path): + with open(output_path, "wb") as output: # Changed -# Example 2 + 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(): @@ -79,7 +91,7 @@ async def slow_coroutine(): asyncio.run(slow_coroutine(), debug=True) -# Example 3 +print("Example 4") from threading import Thread class WriteThread(Thread): @@ -91,7 +103,7 @@ def __init__(self, output_path): def run(self): asyncio.set_event_loop(self.loop) - with open(self.output_path, 'wb') as self.output: + with open(self.output_path, "wb") as self.output: self.loop.run_forever() # Run one final round of callbacks so the await on @@ -99,7 +111,7 @@ def run(self): self.loop.run_until_complete(asyncio.sleep(0)) -# Example 4 + print("Example 5") async def real_write(self, data): self.output.write(data) @@ -110,7 +122,7 @@ async def write(self, data): await asyncio.wrap_future(future) -# Example 5 + print("Example 6") async def real_stop(self): self.loop.stop() @@ -121,7 +133,7 @@ async def stop(self): await asyncio.wrap_future(future) -# Example 6 + print("Example 7") async def __aenter__(self): loop = asyncio.get_event_loop() await loop.run_in_executor(None, self.start) @@ -131,7 +143,7 @@ async def __aexit__(self, *_): await self.stop() -# Example 7 +print("Example 8") class NoNewData(Exception): pass @@ -151,25 +163,24 @@ async def tail_async(handle, interval, write_func): while not handle.closed: try: - line = await loop.run_in_executor( - None, readline, handle) + 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 = [] + async with ( + WriteThread(output_path) as output, + asyncio.TaskGroup() as group, + ): for handle in handles: - coro = tail_async(handle, interval, output.write) - task = asyncio.create_task(coro) - tasks.append(task) - - await asyncio.gather(*tasks) + group.create_task( + tail_async(handle, interval, output.write) + ) -# Example 8 +print("Example 9") # This is all code to simulate the writers to the handles import collections import os @@ -178,11 +189,10 @@ async def run_fully_async(handles, interval, output_path): from tempfile import TemporaryDirectory def write_random_data(path, write_count, interval): - with open(path, 'wb') as f: + 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) + letters = random.choices(string.ascii_lowercase, k=10) data = f'{path}-{i:02}-{"".join(letters)}\n' f.write(data.encode()) f.flush() @@ -191,7 +201,7 @@ 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'): + with open(path, "w"): # Make sure the file at this path will exist when # the reading thread tries to poll it. pass @@ -212,19 +222,19 @@ def setup(): handles = [] for path in input_paths: - handle = open(path, 'rb') + handle = open(path, "rb") handles.append(handle) Thread(target=close_all, args=(handles,)).start() - output_path = os.path.join(tmpdir.name, 'merged') + output_path = os.path.join(tmpdir.name, "merged") return tmpdir, input_paths, handles, output_path -# Example 9 +print("Example 10") def confirm_merge(input_paths, output_path): found = collections.defaultdict(list) - with open(output_path, 'rb') as f: + with open(output_path, "rb") as f: for line in f: for path in input_paths: if line.find(path.encode()) == 0: @@ -232,7 +242,7 @@ def confirm_merge(input_paths, output_path): expected = collections.defaultdict(list) for path in input_paths: - with open(path, 'rb') as f: + with open(path, "rb") as f: expected[path].extend(f.readlines()) for key, expected_lines in expected.items(): diff --git a/example_code/item_64/parallel/my_module.py b/example_code/item_079/parallel/my_module.py similarity index 87% rename from example_code/item_64/parallel/my_module.py rename to example_code/item_079/parallel/my_module.py index 1027356..a61a84b 100755 --- a/example_code/item_64/parallel/my_module.py +++ b/example_code/item_079/parallel/my_module.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,4 +20,4 @@ def gcd(pair): for i in range(low, 0, -1): if a % i == 0 and b % i == 0: return i - assert False, 'Not reachable' + raise RuntimeError("Not reachable") diff --git a/example_code/item_64/parallel/run_parallel.py b/example_code/item_079/parallel/run_parallel.py similarity index 60% rename from example_code/item_64/parallel/run_parallel.py rename to example_code/item_079/parallel/run_parallel.py index 92f6d46..4103f2a 100755 --- a/example_code/item_64/parallel/run_parallel.py +++ b/example_code/item_079/parallel/run_parallel.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,20 +19,25 @@ import time NUMBERS = [ - (1963309, 2265973), (2030677, 3814172), - (1551645, 2229620), (2039045, 2020802), - (1823712, 1924928), (2293129, 1020491), - (1281238, 2273782), (3823812, 4237281), - (3812741, 4729139), (1292391, 2123811), + (19633090, 22659730), + (20306770, 38141720), + (15516450, 22296200), + (20390450, 20208020), + (18237120, 19249280), + (22931290, 10204910), + (12812380, 22737820), + (38238120, 42372810), + (38127410, 47291390), + (12923910, 21238110), ] def main(): - start = time.time() - pool = ProcessPoolExecutor(max_workers=2) # The one change + start = time.perf_counter() + pool = ProcessPoolExecutor(max_workers=8) # The one change results = list(pool.map(my_module.gcd, NUMBERS)) - end = time.time() + end = time.perf_counter() delta = end - start - print(f'Took {delta:.3f} seconds') + print(f"Took {delta:.3f} seconds") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/example_code/item_64/parallel/run_serial.py b/example_code/item_079/parallel/run_serial.py similarity index 62% rename from example_code/item_64/parallel/run_serial.py rename to example_code/item_079/parallel/run_serial.py index 4188579..ba218bf 100755 --- a/example_code/item_64/parallel/run_serial.py +++ b/example_code/item_079/parallel/run_serial.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,19 +18,24 @@ import time NUMBERS = [ - (1963309, 2265973), (2030677, 3814172), - (1551645, 2229620), (2039045, 2020802), - (1823712, 1924928), (2293129, 1020491), - (1281238, 2273782), (3823812, 4237281), - (3812741, 4729139), (1292391, 2123811), + (19633090, 22659730), + (20306770, 38141720), + (15516450, 22296200), + (20390450, 20208020), + (18237120, 19249280), + (22931290, 10204910), + (12812380, 22737820), + (38238120, 42372810), + (38127410, 47291390), + (12923910, 21238110), ] def main(): - start = time.time() + start = time.perf_counter() results = list(map(my_module.gcd, NUMBERS)) - end = time.time() + end = time.perf_counter() delta = end - start - print(f'Took {delta:.3f} seconds') + print(f"Took {delta:.3f} seconds") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/example_code/item_64/parallel/run_threads.py b/example_code/item_079/parallel/run_threads.py similarity index 60% rename from example_code/item_64/parallel/run_threads.py rename to example_code/item_079/parallel/run_threads.py index e89e78b..3eebc1a 100755 --- a/example_code/item_64/parallel/run_threads.py +++ b/example_code/item_079/parallel/run_threads.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,20 +19,25 @@ import time NUMBERS = [ - (1963309, 2265973), (2030677, 3814172), - (1551645, 2229620), (2039045, 2020802), - (1823712, 1924928), (2293129, 1020491), - (1281238, 2273782), (3823812, 4237281), - (3812741, 4729139), (1292391, 2123811), + (19633090, 22659730), + (20306770, 38141720), + (15516450, 22296200), + (20390450, 20208020), + (18237120, 19249280), + (22931290, 10204910), + (12812380, 22737820), + (38238120, 42372810), + (38127410, 47291390), + (12923910, 21238110), ] -def main(): - start = time.time() - pool = ThreadPoolExecutor(max_workers=2) +def main(): + start = time.perf_counter() + pool = ThreadPoolExecutor(max_workers=8) results = list(pool.map(my_module.gcd, NUMBERS)) - end = time.time() + end = time.perf_counter() delta = end - start - print(f'Took {delta:.3f} seconds') + print(f"Took {delta:.3f} seconds") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/example_code/item_65.py b/example_code/item_080.py similarity index 55% rename from example_code/item_65.py rename to example_code/item_080.py index 8929b87..743f652 100755 --- a/example_code/item_65.py +++ b/example_code/item_080.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,30 +44,32 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") def try_finally_example(filename): - print('* Opening file') - handle = open(filename, encoding='utf-8') # May raise OSError + print("* Opening file") + handle = open(filename, encoding="utf-8") # May raise OSError try: - print('* Reading data') - return handle.read() # May raise UnicodeDecodeError + print("* Reading data") + return handle.read() # May raise UnicodeDecodeError finally: - print('* Calling close()') - handle.close() # Always runs after try block + print("* Calling close()") + handle.close() # Always runs after try block -# Example 2 +print("Example 2") try: - filename = 'random_data.txt' + filename = "random_data.txt" - with open(filename, 'wb') as f: - f.write(b'\xf1\xf2\xf3\xf4\xf5') # Invalid utf-8 + 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') @@ -75,106 +77,105 @@ def try_finally_example(filename): assert False -# Example 3 +print("Example 3") try: - try_finally_example('does_not_exist.txt') + try_finally_example("does_not_exist.txt") except: logging.exception('Expected') else: assert False -# Example 4 +print("Example 4") import json def load_json_key(data, key): try: - print('* Loading JSON data') + print("* Loading JSON data") result_dict = json.loads(data) # May raise ValueError - except ValueError as e: - print('* Handling ValueError') - raise KeyError(key) from e + except ValueError: + print("* Handling ValueError") + raise KeyError(key) else: - print('* Looking up key') + print("* Looking up key") return result_dict[key] # May raise KeyError -# Example 5 -assert load_json_key('{"foo": "bar"}', 'foo') == 'bar' +print("Example 5") +assert load_json_key('{"foo": "bar"}', "foo") == "bar" -# Example 6 +print("Example 6") try: - load_json_key('{"foo": bad payload', 'foo') + load_json_key('{"foo": bad payload', "foo") except: logging.exception('Expected') else: assert False -# Example 7 +print("Example 7") try: - load_json_key('{"foo": "bar"}', 'does not exist') + load_json_key('{"foo": "bar"}', "does not exist") except: logging.exception('Expected') else: assert False -# Example 8 +print("Example 8") UNDEFINED = object() DIE_IN_ELSE_BLOCK = False def divide_json(path): - print('* Opening file') - handle = open(path, 'r+') # May raise OSError + 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') + 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 + print("* Writing calculation") + op["result"] = value result = json.dumps(op) - handle.seek(0) # May raise OSError + 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 + handle.write(result) # May raise OSError return value finally: - print('* Calling close()') - handle.close() # Always runs + print("* Calling close()") + handle.close() # Always runs -# Example 9 -temp_path = 'random_data.json' +print("Example 9") +temp_path = "random_data.json" -with open(temp_path, 'w') as f: +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: +print("Example 10") +with open(temp_path, "w") as f: f.write('{"numerator": 1, "denominator": 0}') assert divide_json(temp_path) is UNDEFINED -# Example 11 +print("Example 11") try: - with open(temp_path, 'w') as f: + with open(temp_path, "w") as f: f.write('{"numerator": 1 bad data') divide_json(temp_path) @@ -184,9 +185,9 @@ def divide_json(path): assert False -# Example 12 +print("Example 12") try: - with open(temp_path, 'w') as f: + with open(temp_path, "w") as f: f.write('{"numerator": 1, "denominator": 10}') DIE_IN_ELSE_BLOCK = True 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_66.py b/example_code/item_082.py similarity index 64% rename from example_code/item_66.py rename to example_code/item_082.py index 213d70a..d1c594a 100755 --- a/example_code/item_66.py +++ b/example_code/item_082.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") from threading import Lock lock = Lock() @@ -55,7 +56,7 @@ def close_open_files(): pass -# Example 2 +print("Example 2") lock.acquire() try: # Do something while maintaining an invariant @@ -64,21 +65,22 @@ def close_open_files(): lock.release() -# Example 3 +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') + logging.debug("Some debug data") + logging.error("Error log here") + logging.debug("More debug data") -# Example 4 +print("Example 4") my_function() -# Example 5 +print("Example 5") from contextlib import contextmanager @contextmanager @@ -92,21 +94,29 @@ def debug_logging(level): logger.setLevel(old_level) -# Example 6 +print("Example 6") with debug_logging(logging.DEBUG): - print('* Inside:') + print("* Inside:") my_function() -print('* After:') +print("* After:") my_function() -# Example 7 -with open('my_output.txt', 'w') as handle: - handle.write('This is some data!') +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() -# Example 8 +print("Example 9") @contextmanager def log_level(level, name): logger = logging.getLogger(name) @@ -118,19 +128,19 @@ def log_level(level, name): 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') +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") -# Example 10 -logger = logging.getLogger('my-log') -logger.debug('Debug will not print') -logger.error('Error will print') +print("Example 11") +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') +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_12.py b/example_code/item_091.py similarity index 63% rename from example_code/item_12.py rename to example_code/item_091.py index 7744737..00f02a0 100755 --- a/example_code/item_12.py +++ b/example_code/item_091.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,56 +44,43 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# 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) +print("Example 1") +x = eval("1 + 2") +print(x) -# Example 4 +print("Example 2") try: - w = '寿司' - x = w.encode('utf-8') - y = x[::-1] - z = y.decode('utf-8') + eval( + """ + if True: + print('okay') + else: + print('no') + """ + ) 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] # [] +print("Example 3") +global_scope = {"my_condition": False} +local_scope = {} +exec( + """ +if my_condition: + x = 'yes' +else: + x = 'no' +""", + global_scope, + local_scope, +) -# Example 7 -y = x[::2] # ['a', 'c', 'e', 'g'] -z = y[1:-1] # ['c', 'e'] -print(x) -print(y) -print(z) +print(local_scope) diff --git a/example_code/item_70.py b/example_code/item_092.py similarity index 81% rename from example_code/item_70.py rename to example_code/item_092.py index 3775662..92f7c35 100755 --- a/example_code/item_70.py +++ b/example_code/item_092.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") def insertion_sort(data): result = [] for value in data: @@ -54,7 +55,7 @@ def insertion_sort(data): return result -# Example 2 +print("Example 2") def insert_value(array, value): for i, existing in enumerate(array): if existing > value: @@ -63,32 +64,32 @@ def insert_value(array, value): array.append(value) -# Example 3 +print("Example 3") from random import randint -max_size = 10**4 +max_size = 12**4 data = [randint(0, max_size) for _ in range(max_size)] test = lambda: insertion_sort(data) -# Example 4 +print("Example 4") from cProfile import Profile profiler = Profile() profiler.runcall(test) -# Example 5 +print("Example 5") from pstats import Stats stats = Stats(profiler) stats = Stats(profiler, stream=STDOUT) stats.strip_dirs() -stats.sort_stats('cumulative') +stats.sort_stats("cumulative") stats.print_stats() -# Example 6 +print("Example 6") from bisect import bisect_left def insert_value(array, value): @@ -96,16 +97,16 @@ def insert_value(array, value): array.insert(i, value) -# Example 7 +print("Example 7") profiler = Profile() profiler.runcall(test) stats = Stats(profiler, stream=STDOUT) stats.strip_dirs() -stats.sort_stats('cumulative') +stats.sort_stats("cumulative") stats.print_stats() -# Example 8 +print("Example 8") def my_utility(a, b): c = 1 for i in range(100): @@ -125,17 +126,24 @@ def my_program(): second_func() -# Example 9 +print("Example 9") profiler = Profile() profiler.runcall(my_program) stats = Stats(profiler, stream=STDOUT) stats.strip_dirs() -stats.sort_stats('cumulative') +stats.sort_stats("cumulative") stats.print_stats() -# Example 10 +print("Example 10") stats = Stats(profiler, stream=STDOUT) stats.strip_dirs() -stats.sort_stats('cumulative') +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..5f36d14 --- /dev/null +++ b/example_code/item_095.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") +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__) +library_path = run_py.parent / "item_095" / "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_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_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/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/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_84_example_06.py b/example_code/item_096/my_extension2/setup.py old mode 100755 new mode 100644 similarity index 68% rename from example_code/item_84_example_06.py rename to example_code/item_096/my_extension2/setup.py index 99a4915..529b231 --- a/example_code/item_84_example_06.py +++ b/example_code/item_096/my_extension2/setup.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,13 +14,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +from setuptools import Extension, setup - -# 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 +setup( + name="my_extension2", + ext_modules=[ + Extension( + name="my_extension2", + sources=["init.c", "dot_product.c"], + ), + ], +) 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_74.py b/example_code/item_099.py similarity index 62% rename from example_code/item_74.py rename to example_code/item_099.py index dfdbdc9..0819f15 100755 --- a/example_code/item_74.py +++ b/example_code/item_099.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") def timecode_to_index(video_id, timecode): return 1234 # Returns the byte offset in the video data @@ -56,16 +57,16 @@ def request_chunk(video_id, byte_offset, size): # Returns size bytes of video_id's data from the offset video_id = ... -timecode = '01:09:14:28' +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 +print("Example 2") class NullSocket: def __init__(self): - self.handle = open(os.devnull, 'wb') + self.handle = open(os.devnull, "wb") def send(self, data): self.handle.write(data) @@ -80,58 +81,66 @@ def send(self, data): video_data = 100 * os.urandom(1024 * 1024) byte_offset = 1234 -chunk = video_data[byte_offset:byte_offset + size] +chunk = video_data[byte_offset : byte_offset + size] socket.send(chunk) -# Example 3 +print("Example 3") import timeit def run_test(): - chunk = video_data[byte_offset:byte_offset + size] + 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 +result = ( + timeit.timeit( + stmt="run_test()", + globals=globals(), + number=100, + ) + / 100 +) -print(f'{result:0.9f} seconds') +print(f"{result:0.9f} seconds") -# Example 4 -data = b'shave and a haircut, two bits' +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("Size: ", chunk.nbytes) +print("Data in view: ", chunk.tobytes()) +print("Underlying data:", chunk.obj) -# Example 5 +print("Example 5") video_view = memoryview(video_data) def run_test(): - chunk = video_view[byte_offset:byte_offset + size] + 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 +result = ( + timeit.timeit( + stmt="run_test()", + globals=globals(), + number=100, + ) + / 100 +) -print(f'{result:0.9f} seconds') +print(f"{result:0.9f} seconds") -# Example 6 +print("Example 6") class FakeSocket: def recv(self, size): - return video_view[byte_offset:byte_offset+size] + return video_view[byte_offset : byte_offset + size] def recv_into(self, buffer): - source_data = video_view[byte_offset:byte_offset+size] + source_data = video_view[byte_offset : byte_offset + size] buffer[:] = source_data socket = ... # socket connection to the client @@ -145,64 +154,72 @@ def recv_into(self, buffer): 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]) +after = video_view[byte_offset + size :] +new_cache = b"".join([before, chunk, after]) -# Example 7 +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]) + after = video_view[byte_offset + size :] + new_cache = b"".join([before, chunk, after]) -result = timeit.timeit( - stmt='run_test()', - globals=globals(), - number=100) / 100 +result = ( + timeit.timeit( + stmt="run_test()", + globals=globals(), + number=100, + ) + / 100 +) -print(f'{result:0.9f} seconds') +print(f"{result:0.9f} seconds") -# Example 8 +print("Example 8") try: - my_bytes = b'hello' - my_bytes[0] = b'\x79' + my_bytes = b"hello" + my_bytes[0] = 0x79 except: logging.exception('Expected') else: assert False -# Example 9 -my_array = bytearray(b'hello') +print("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') +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-' +write_view[:] = b"-10 bytes-" print(my_array) -# Example 11 +print("Example 11") video_array = bytearray(video_cache) write_view = memoryview(video_array) -chunk = write_view[byte_offset:byte_offset + size] +chunk = write_view[byte_offset : byte_offset + size] socket.recv_into(chunk) -# Example 12 +print("Example 12") def run_test(): - chunk = write_view[byte_offset:byte_offset + size] + chunk = write_view[byte_offset : byte_offset + size] socket.recv_into(chunk) -result = timeit.timeit( - stmt='run_test()', - globals=globals(), - number=100) / 100 +result = ( + timeit.timeit( + stmt="run_test()", + globals=globals(), + number=100, + ) + / 100 +) -print(f'{result:0.9f} seconds') +print(f"{result:0.9f} seconds") diff --git a/example_code/item_14.py b/example_code/item_100.py similarity index 60% rename from example_code/item_14.py rename to example_code/item_100.py index 271663a..ed06714 100755 --- a/example_code/item_14.py +++ b/example_code/item_100.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,32 +44,33 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") numbers = [93, 86, 11, 68, 70] numbers.sort() print(numbers) -# Example 2 +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})' + 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), + Tool("level", 3.5), + Tool("hammer", 1.25), + Tool("screwdriver", 0.5), + Tool("chisel", 0.25), ] -# Example 3 +print("Example 3") try: tools.sort() except: @@ -78,89 +79,94 @@ def __repr__(self): assert False -# Example 4 -print('Unsorted:', repr(tools)) +print("Example 4") +print("Unsorted:", repr(tools)) tools.sort(key=lambda x: x.name) -print('\nSorted: ', tools) +print("\nSorted: ", tools) -# Example 5 +print("Example 5") tools.sort(key=lambda x: x.weight) -print('By weight:', tools) +print("By weight:", tools) -# Example 6 -places = ['home', 'work', 'New York', 'Paris'] +print("Example 6") +places = ["home", "work", "New York", "Paris"] places.sort() -print('Case sensitive: ', places) +print("Case sensitive: ", places) places.sort(key=lambda x: x.lower()) -print('Case insensitive:', places) +print("Case insensitive:", places) -# Example 7 +print("Example 7") power_tools = [ - Tool('drill', 4), - Tool('circular saw', 5), - Tool('jackhammer', 40), - Tool('sander', 4), + Tool("drill", 4), + Tool("circular saw", 5), + Tool("jackhammer", 40), + Tool("sander", 4), ] -# Example 8 -saw = (5, 'circular saw') -jackhammer = (40, 'jackhammer') +print("Example 8") +saw = (5, "circular saw") +jackhammer = (40, "jackhammer") assert not (jackhammer < saw) # Matches expectations -# Example 9 -drill = (4, 'drill') -sander = (4, 'sander') +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 -# Example 10 +print("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("Example 11") +power_tools.sort( + key=lambda x: (x.weight, x.name), + reverse=True, # Makes all criteria descending +) print(power_tools) -# Example 12 +print("Example 12") power_tools.sort(key=lambda x: (-x.weight, x.name)) print(power_tools) -# Example 13 +print("Example 13") try: - power_tools.sort(key=lambda x: (x.weight, -x.name), - reverse=True) + 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("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 +print("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("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_72.py b/example_code/item_102.py similarity index 74% rename from example_code/item_72.py rename to example_code/item_102.py index 078201a..b546c52 100755 --- a/example_code/item_72.py +++ b/example_code/item_102.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,20 +44,21 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") data = list(range(10**5)) index = data.index(91234) assert index == 91234 -# Example 2 +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') + raise ValueError(f"{goal} is out of bounds") index = find_closest(data, 91234.56) assert index == 91235 @@ -70,7 +71,7 @@ def find_closest(sequence, goal): assert False -# Example 3 +print("Example 3") from bisect import bisect_left index = bisect_left(data, 91234) # Exact match @@ -80,7 +81,7 @@ def find_closest(sequence, goal): assert index == 91235 -# Example 4 +print("Example 4") import random import timeit @@ -88,8 +89,7 @@ def find_closest(sequence, goal): iterations = 1000 data = list(range(size)) -to_lookup = [random.randint(0, size) - for _ in range(iterations)] +to_lookup = [random.randint(0, size) for _ in range(iterations)] def run_linear(data, to_lookup): for index in to_lookup: @@ -99,17 +99,25 @@ 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') +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 time') +print(f"{slowdown:.1f}x slower") diff --git a/example_code/item_71.py b/example_code/item_103.py similarity index 55% rename from example_code/item_71.py rename to example_code/item_103.py index bf16a0a..e4a691a 100755 --- a/example_code/item_71.py +++ b/example_code/item_103.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") class Email: def __init__(self, sender, receiver, message): self.sender = sender @@ -54,18 +55,18 @@ def __init__(self, sender, receiver, message): self.message = message -# Example 2 +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 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 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 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 Email("peanut@example.com", "alice@example.com", "hello7") yield None EMAIL_IT = get_emails() @@ -83,11 +84,11 @@ def try_receive_email(): if not email: raise NoEmailError - print(f'Produced email: {email.message}') + print(f"Produced email: {email.message}") return email -# Example 3 +print("Example 3") def produce_emails(queue): while True: try: @@ -98,23 +99,24 @@ def produce_emails(queue): queue.append(email) # Producer -# Example 4 +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(f"Consumed email: {email.message}") -# Example 5 +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)) + count = list(range(10)) def func(): if count: @@ -132,45 +134,30 @@ def my_end_func(): loop([], my_end_func) -# Example 6 +print("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)', + return timeit.timeit( + setup="queue = []", + stmt="run(queue)", globals=locals(), - repeat=1000, - number=1) - - return print_results(count, tests) + number=1, + ) -# 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') +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") -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 +print("Example 8") def list_pop_benchmark(count): def prepare(): return list(range(count)) @@ -179,25 +166,22 @@ def run(queue): while queue: queue.pop(0) - tests = timeit.repeat( - setup='queue = prepare()', - stmt='run(queue)', + return timeit.timeit( + setup="queue = prepare()", + stmt="run(queue)", globals=locals(), - repeat=1000, - number=1) - - return print_results(count, tests) + number=1, + ) -# 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) +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") -# Example 10 +print("Example 10") import collections def consume_one_email(queue): @@ -205,7 +189,7 @@ def consume_one_email(queue): return email = queue.popleft() # Consumer # Process the email message - print(f'Consumed email: {email.message}') + print(f"Consumed email: {email.message}") def my_end_func(): pass @@ -215,7 +199,7 @@ def my_end_func(): loop(collections.deque(), my_end_func) -# Example 11 +print("Example 11") def deque_append_benchmark(count): def prepare(): return collections.deque() @@ -224,22 +208,20 @@ def run(queue): for i in range(count): queue.append(i) - tests = timeit.repeat( - setup='queue = prepare()', - stmt='run(queue)', + return timeit.timeit( + setup="queue = prepare()", + stmt="run(queue)", globals=locals(), - repeat=1000, - number=1) - return print_results(count, tests) + number=1, + ) -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) +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") -# Example 12 +print("Example 12") def dequeue_popleft_benchmark(count): def prepare(): return collections.deque(range(count)) @@ -248,17 +230,14 @@ def run(queue): while queue: queue.popleft() - tests = timeit.repeat( - setup='queue = prepare()', - stmt='run(queue)', + return timeit.timeit( + setup="queue = prepare()", + stmt="run(queue)", globals=locals(), - repeat=1000, - number=1) - - return print_results(count, tests) + number=1, + ) -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) +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_73.py b/example_code/item_104.py similarity index 58% rename from example_code/item_73.py rename to example_code/item_104.py index 352d6ac..9073442 100755 --- a/example_code/item_73.py +++ b/example_code/item_104.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,28 +44,29 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") class Book: def __init__(self, title, due_date): self.title = title self.due_date = due_date -# Example 2 +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')) +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 +print("Example 3") class NoOverdueBooks(Exception): pass @@ -79,31 +80,31 @@ def next_overdue_book(queue, now): raise NoOverdueBooks -# Example 4 -now = '2019-06-10' +print("Example 4") +now = "2019-06-10" found = next_overdue_book(queue, now) -print(found.title) +print(found.due_date, found.title) found = next_overdue_book(queue, now) -print(found.title) +print(found.due_date, found.title) -# Example 5 +print("Example 5") def return_book(queue, book): queue.remove(book) queue = [] -book = Book('Treasure Island', '2019-06-04') +book = Book("Treasure Island", "2019-06-04") add_book(queue, book) -print('Before return:', [x.title for x in queue]) +print("Before return:", [x.title for x in queue]) return_book(queue, book) -print('After return: ', [x.title for x in queue]) +print("After return: ", [x.title for x in queue]) -# Example 6 +print("Example 6") try: next_overdue_book(queue, now) except NoOverdueBooks: @@ -112,22 +113,10 @@ def return_book(queue, book): assert False # Doesn't happen -# Example 7 +print("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)) @@ -142,25 +131,22 @@ def run(queue, to_add): while queue: queue.pop() - tests = timeit.repeat( - setup='queue, to_add = prepare()', - stmt=f'run(queue, to_add)', + return timeit.timeit( + setup="queue, to_add = prepare()", + stmt=f"run(queue, to_add)", globals=locals(), - repeat=100, - number=1) - - return print_results(count, tests) + number=1, + ) -# 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) +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") -# Example 9 +print("Example 9") def list_return_benchmark(count): def prepare(): queue = list(range(count)) @@ -175,43 +161,40 @@ 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)', + return timeit.timeit( + setup="queue, to_return = prepare()", + stmt=f"run(queue, to_return)", globals=locals(), - repeat=100, - number=1) + number=1, + ) - return print_results(count, tests) +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") -# 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 +print("Example 11") from heapq import heappush def add_book(queue, book): heappush(queue, book) -# Example 12 +print("Example 12") try: queue = [] - add_book(queue, Book('Little Women', '2019-06-05')) - add_book(queue, Book('The Time Machine', '2019-05-30')) + 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 +print("Example 13") import functools @functools.total_ordering @@ -224,70 +207,70 @@ def __lt__(self, other): return self.due_date < other.due_date -# Example 14 +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')) +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 +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'), + 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 +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'), + 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 +print("Example 17") from heapq import heappop def next_overdue_book(queue, now): if queue: - book = queue[0] # Most overdue first + book = queue[0] # Most overdue first if book.due_date < now: - heappop(queue) # Remove the overdue book + heappop(queue) # Remove the overdue book return book raise NoOverdueBooks -# Example 18 -now = '2019-06-02' +print("Example 18") +now = "2019-06-02" book = next_overdue_book(queue, now) -print(book.title) +print(book.due_date, book.title) book = next_overdue_book(queue, now) -print(book.title) +print(book.due_date, book.title) try: next_overdue_book(queue, now) except NoOverdueBooks: - pass # Expected + pass # Expected else: assert False # Doesn't happen -# Example 19 +print("Example 19") def heap_overdue_benchmark(count): def prepare(): to_add = list(range(count)) @@ -300,25 +283,22 @@ def run(queue, to_add): while queue: heappop(queue) - tests = timeit.repeat( - setup='queue, to_add = prepare()', - stmt=f'run(queue, to_add)', + return timeit.timeit( + setup="queue, to_add = prepare()", + stmt=f"run(queue, to_add)", globals=locals(), - repeat=100, - number=1) + number=1, + ) - return print_results(count, tests) +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") -# 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 +print("Example 21") @functools.total_ordering class Book: def __init__(self, title, due_date): @@ -330,7 +310,7 @@ def __lt__(self, other): return self.due_date < other.due_date -# Example 22 +print("Example 22") def next_overdue_book(queue, now): while queue: book = queue[0] @@ -346,39 +326,41 @@ def next_overdue_book(queue, now): raise NoOverdueBooks + queue = [] -book = Book('Pride and Prejudice', '2019-06-01') +book = Book("Pride and Prejudice", "2019-06-01") add_book(queue, book) -book = Book('The Time Machine', '2019-05-30') +book = Book("The Time Machine", "2019-05-30") add_book(queue, book) book.returned = True -book = Book('Crime and Punishment', '2019-06-06') +book = Book("Crime and Punishment", "2019-06-06") add_book(queue, book) book.returned = True -book = Book('Wuthering Heights', '2019-06-12') +book = Book("Wuthering Heights", "2019-06-12") add_book(queue, book) -now = '2019-06-11' +now = "2019-06-11" book = next_overdue_book(queue, now) -assert book.title == 'Pride and Prejudice' +assert book.title == "Pride and Prejudice" try: next_overdue_book(queue, now) except NoOverdueBooks: - pass # Expected + pass # Expected else: assert False # Doesn't happen -# Example 23 +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_67.py b/example_code/item_105.py similarity index 61% rename from example_code/item_67.py rename to example_code/item_105.py index 1a75166..50d3523 100755 --- a/example_code/item_67.py +++ b/example_code/item_105.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,82 +44,79 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") import time -now = 1552774475 +now = 1710047865.0 local_tuple = time.localtime(now) -time_format = '%Y-%m-%d %H:%M:%S' +time_format = "%Y-%m-%d %H:%M:%S" time_str = time.strftime(time_format, local_tuple) print(time_str) -# Example 2 +print("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) +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) -# Example 4 +print("Example 4") try: - arrival_nyc = '2019-03-16 23:33:24 EDT' - time_tuple = time.strptime(arrival_nyc, time_format) + arrival_nyc = "2024-03-10 03:31:18 EDT" + time_tuple = time.strptime(arrival_nyc, parse_format) except: logging.exception('Expected') else: assert False -# Example 5 +print("Example 5") from datetime import datetime, timezone -now = datetime(2019, 3, 16, 22, 14, 35) +now = datetime(2024, 3, 10, 5, 17, 45) 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' +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) -# Example 7 -import pytz +print("Example 7") +from zoneinfo import ZoneInfo -arrival_nyc = '2019-03-16 23:33:24' +arrival_nyc = "2024-03-10 03:31:18" 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) +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) -# Example 8 -pacific = pytz.timezone('US/Pacific') -sf_dt = pacific.normalize(utc_dt.astimezone(pacific)) -print(sf_dt) +print("Example 8") +pacific = ZoneInfo("US/Pacific") +sf_dt = utc_dt.astimezone(pacific) +print("PST:", sf_dt) -# Example 9 -nepal = pytz.timezone('Asia/Katmandu') -nepal_dt = nepal.normalize(utc_dt.astimezone(nepal)) -print(nepal_dt) +print("Example 9") +nepal = ZoneInfo("Asia/Katmandu") +nepal_dt = utc_dt.astimezone(nepal) +print("NPT", nepal_dt) diff --git a/example_code/item_69.py b/example_code/item_106.py similarity index 69% rename from example_code/item_69.py rename to example_code/item_106.py index 1e18676..5974021 100755 --- a/example_code/item_69.py +++ b/example_code/item_106.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,56 +44,57 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") rate = 1.45 -seconds = 3*60 + 42 +seconds = 3 * 60 + 42 cost = rate * seconds / 60 print(cost) -# Example 2 +print("Example 2") print(round(cost, 2)) -# Example 3 +print("Example 3") from decimal import Decimal -rate = Decimal('1.45') -seconds = Decimal(3*60 + 42) +rate = Decimal("1.45") +seconds = Decimal(3 * 60 + 42) cost = rate * seconds / Decimal(60) print(cost) -# Example 4 -print(Decimal('1.45')) +print("Example 4") +print(Decimal("1.45")) print(Decimal(1.45)) -# Example 5 -print('456') +print("Example 5") +print("456") print(456) -# Example 6 -rate = Decimal('0.05') -seconds = Decimal('5') +print("Example 6") +rate = Decimal("0.05") +seconds = Decimal("5") small_cost = rate * seconds / Decimal(60) print(small_cost) -# Example 7 +print("Example 7") print(round(small_cost, 2)) -# Example 8 +print("Example 8") from decimal import ROUND_UP -rounded = cost.quantize(Decimal('0.01'), rounding=ROUND_UP) -print(f'Rounded {cost} to {rounded}') +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}') +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_68.py b/example_code/item_107.py similarity index 79% rename from example_code/item_68.py rename to example_code/item_107.py index 4b9eb5f..05c9099 100755 --- a/example_code/item_68.py +++ b/example_code/item_107.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,16 +44,17 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") class GameState: def __init__(self): self.level = 0 self.lives = 4 -# Example 2 +print("Example 2") state = GameState() state.level += 1 # Player beat a level state.lives -= 1 # Player had to try again @@ -61,22 +62,22 @@ def __init__(self): print(state.__dict__) -# Example 3 +print("Example 3") import pickle -state_path = 'game_state.bin' -with open(state_path, 'wb') as f: +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: +print("Example 4") +with open(state_path, "rb") as f: state_after = pickle.load(f) print(state_after.__dict__) -# Example 5 +print("Example 5") class GameState: def __init__(self): self.level = 0 @@ -84,25 +85,26 @@ def __init__(self): self.points = 0 # New field -# Example 6 +print("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: +print("Example 7") +with open(state_path, "rb") as f: state_after = pickle.load(f) print(state_after.__dict__) -# Example 8 +print("Example 8") assert isinstance(state_after, GameState) -# Example 9 +print("Example 9") class GameState: def __init__(self, level=0, lives=4, points=0): self.level = level @@ -110,24 +112,24 @@ def __init__(self, level=0, lives=4, points=0): self.points = points -# Example 10 +print("Example 10") def pickle_game_state(game_state): kwargs = game_state.__dict__ return unpickle_game_state, (kwargs,) -# Example 11 +print("Example 11") def unpickle_game_state(kwargs): return GameState(**kwargs) -# Example 12 +print("Example 12") import copyreg copyreg.pickle(GameState, pickle_game_state) -# Example 13 +print("Example 13") state = GameState() state.points += 1000 serialized = pickle.dumps(state) @@ -135,7 +137,7 @@ def unpickle_game_state(kwargs): print(state_after.__dict__) -# Example 14 +print("Example 14") class GameState: def __init__(self, level=0, lives=4, points=0, magic=5): self.level = level @@ -144,13 +146,13 @@ def __init__(self, level=0, lives=4, points=0, magic=5): self.magic = magic # New field -# Example 15 -print('Before:', state.__dict__) +print("Example 15") +print("Before:", state.__dict__) state_after = pickle.loads(serialized) -print('After: ', state_after.__dict__) +print("After: ", state_after.__dict__) -# Example 16 +print("Example 16") class GameState: def __init__(self, level=0, points=0, magic=5): self.level = level @@ -158,7 +160,7 @@ def __init__(self, level=0, points=0, magic=5): self.magic = magic -# Example 17 +print("Example 17") try: pickle.loads(serialized) except: @@ -167,33 +169,34 @@ def __init__(self, level=0, points=0, magic=5): assert False -# Example 18 +print("Example 18") def pickle_game_state(game_state): kwargs = game_state.__dict__ - kwargs['version'] = 2 + kwargs["version"] = 2 return unpickle_game_state, (kwargs,) -# Example 19 +print("Example 19") def unpickle_game_state(kwargs): - version = kwargs.pop('version', 1) + version = kwargs.pop("version", 1) if version == 1: - del kwargs['lives'] + del kwargs["lives"] return GameState(**kwargs) -# Example 20 +print("Example 20") copyreg.pickle(GameState, pickle_game_state) -print('Before:', state.__dict__) +print("Before:", state.__dict__) state_after = pickle.loads(serialized) -print('After: ', state_after.__dict__) +print("After: ", state_after.__dict__) -# Example 21 +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 @@ -201,7 +204,7 @@ def __init__(self, level=0, points=0, magic=5): self.magic = magic -# Example 22 +print("Example 22") try: pickle.loads(serialized) except: @@ -210,15 +213,15 @@ def __init__(self, level=0, points=0, magic=5): assert False -# Example 23 +print("Example 23") print(serialized) -# Example 24 +print("Example 24") copyreg.pickle(BetterGameState, pickle_game_state) -# Example 25 +print("Example 25") state = BetterGameState() serialized = pickle.dumps(state) print(serialized) diff --git a/example_code/item_76/testing/assert_test.py b/example_code/item_108/testing/assert_test.py similarity index 91% rename from example_code/item_76/testing/assert_test.py rename to example_code/item_108/testing/assert_test.py index ebf9bb5..8ff62cb 100755 --- a/example_code/item_76/testing/assert_test.py +++ b/example_code/item_108/testing/assert_test.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -28,5 +28,5 @@ def test_assert_statement(self): found = 2 * 5 assert expected == found -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/example_code/item_76/testing/data_driven_test.py b/example_code/item_108/testing/data_driven_test.py similarity index 80% rename from example_code/item_76/testing/data_driven_test.py rename to example_code/item_108/testing/data_driven_test.py index 8255196..b08c614 100755 --- a/example_code/item_76/testing/data_driven_test.py +++ b/example_code/item_108/testing/data_driven_test.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,9 +20,9 @@ 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'), + (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): @@ -31,12 +31,12 @@ def test_good(self): def test_bad(self): bad_cases = [ (object(), TypeError), - (b'\xfa\xfa', UnicodeDecodeError), + (b"\xfa\xfa", UnicodeDecodeError), ] for value, exception in bad_cases: with self.subTest(value): with self.assertRaises(exception): to_str(value) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/example_code/item_76/testing/helper_test.py b/example_code/item_108/testing/helper_test.py similarity index 65% rename from example_code/item_76/testing/helper_test.py rename to example_code/item_108/testing/helper_test.py index 6796b2a..c2d734a 100755 --- a/example_code/item_76/testing/helper_test.py +++ b/example_code/item_108/testing/helper_test.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,40 +19,30 @@ def sum_squares(values): cumulative = 0 for value in values: - cumulative += value ** 2 + 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) + test_it = zip(expect_it, found_it, strict=True) for i, (expect, found) in enumerate(test_it): - self.assertEqual( - expect, - found, - f'Index {i} is wrong') + if found != expect: + self.fail(f"Index {i} is wrong: {found} != {expect}") - # 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_too_short(self): + values = [1.1, 2.2] + expected = [1.1**2] + self.verify_complex_case(values, expected) - def test_wrong_lengths(self): - values = [1.1, 2.2, 3.3] + 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) @@ -65,5 +55,5 @@ def test_wrong_results(self): ] self.verify_complex_case(values, expected) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/example_code/item_76/testing/utils.py b/example_code/item_108/testing/utils.py similarity index 78% rename from example_code/item_76/testing/utils.py rename to example_code/item_108/testing/utils.py index 30b0787..7d14a78 100755 --- a/example_code/item_76/testing/utils.py +++ b/example_code/item_108/testing/utils.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ def to_str(data): if isinstance(data, str): return data elif isinstance(data, bytes): - return data.decode('utf-8') + return data.decode("utf-8") else: - raise TypeError('Must supply str or bytes, ' - 'found: %r' % data) + raise TypeError(f"Must supply str or bytes, found: {data}") diff --git a/example_code/item_76/testing/utils_error_test.py b/example_code/item_108/testing/utils_error_test.py similarity index 88% rename from example_code/item_76/testing/utils_error_test.py rename to example_code/item_108/testing/utils_error_test.py index 36ccbbe..a47f9b8 100755 --- a/example_code/item_76/testing/utils_error_test.py +++ b/example_code/item_108/testing/utils_error_test.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ def test_to_str_bad(self): def test_to_str_bad_encoding(self): with self.assertRaises(UnicodeDecodeError): - to_str(b'\xfa\xfa') + to_str(b"\xfa\xfa") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/example_code/item_76/testing/utils_test.py b/example_code/item_108/testing/utils_test.py similarity index 76% rename from example_code/item_76/testing/utils_test.py rename to example_code/item_108/testing/utils_test.py index 258dcd6..56fe1a8 100755 --- a/example_code/item_76/testing/utils_test.py +++ b/example_code/item_108/testing/utils_test.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,13 +19,13 @@ class UtilsTestCase(TestCase): def test_to_str_bytes(self): - self.assertEqual('hello', to_str(b'hello')) + self.assertEqual("hello", to_str(b"hello")) def test_to_str_str(self): - self.assertEqual('hello', to_str('hello')) + self.assertEqual("hello", to_str("hello")) def test_failing(self): - self.assertEqual('incorrect', to_str('hello')) + self.assertEqual("incorrect", to_str("hello")) -if __name__ == '__main__': +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_77/testing/environment_test.py b/example_code/item_110/testing/environment_test.py similarity index 84% rename from example_code/item_77/testing/environment_test.py rename to example_code/item_110/testing/environment_test.py index 527f958..500c6f2 100755 --- a/example_code/item_77/testing/environment_test.py +++ b/example_code/item_110/testing/environment_test.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -27,8 +27,8 @@ 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') + with open(self.test_path / "data.bin", "w") as f: + f.write("hello") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/example_code/item_77/testing/integration_test.py b/example_code/item_110/testing/integration_test.py similarity index 76% rename from example_code/item_77/testing/integration_test.py rename to example_code/item_110/testing/integration_test.py index c722c87..dbc614b 100755 --- a/example_code/item_77/testing/integration_test.py +++ b/example_code/item_110/testing/integration_test.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,23 +17,23 @@ from unittest import TestCase, main def setUpModule(): - print('* Module setup') + print("* Module setup") def tearDownModule(): - print('* Module clean-up') + print("* Module clean-up") class IntegrationTest(TestCase): def setUp(self): - print('* Test setup') + print("* Test setup") def tearDown(self): - print('* Test clean-up') + print("* Test clean-up") def test_end_to_end1(self): - print('* Test 1') + print("* Test 1") def test_end_to_end2(self): - print('* Test 2') + print("* Test 2") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/example_code/item_78.py b/example_code/item_111.py similarity index 56% rename from example_code/item_78.py rename to example_code/item_111.py index 49f9c0f..2ce0c85 100755 --- a/example_code/item_78.py +++ b/example_code/item_111.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,47 +44,49 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +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') + raise DatabaseConnectionError("Not connected") # Return a list of (name, last_mealtime) tuples -# Example 2 +print("Example 2") try: - database = DatabaseConnection('localhost', '4444') + database = DatabaseConnection("localhost", "4444") - get_animals(database, 'Meerkat') + get_animals(database, "Meerkat") except: logging.exception('Expected') else: assert False -# Example 3 +print("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)), + ("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 -# Example 4 +print("Example 4") try: mock.does_not_exist except: @@ -93,51 +95,51 @@ def get_animals(database, species): assert False -# Example 5 +print("Example 5") database = object() -result = mock(database, 'Meerkat') +result = mock(database, "Meerkat") assert result == expected -# Example 6 -mock.assert_called_once_with(database, 'Meerkat') +print("Example 6") +mock.assert_called_once_with(database, "Meerkat") -# Example 7 +print("Example 7") try: - mock.assert_called_once_with(database, 'Giraffe') + mock.assert_called_once_with(database, "Giraffe") except: logging.exception('Expected') else: assert False -# Example 8 +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("database 1", "Rabbit") +mock("database 2", "Bison") +mock("database 3", "Meerkat") -mock.assert_called_with(ANY, 'Meerkat') +mock.assert_called_with(ANY, "Meerkat") -# Example 9 +print("Example 9") try: class MyError(Exception): pass mock = Mock(spec=get_animals) - mock.side_effect = MyError('Whoops! Big problem') - result = mock(database, 'Meerkat') + mock.side_effect = MyError("Whoops! Big problem") + result = mock(database, "Meerkat") except: logging.exception('Expected') else: assert False -# Example 10 +print("Example 10") def get_food_period(database, species): # Query the Database pass @@ -148,7 +150,7 @@ def feed_animal(database, name, when): pass def do_rounds(database, species): - now = datetime.datetime.utcnow() + now = datetime.now() feeding_timedelta = get_food_period(database, species) animals = get_animals(database, species) fed = 0 @@ -161,12 +163,16 @@ def do_rounds(database, species): 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): +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) @@ -180,89 +186,96 @@ def do_rounds(database, species, *, return fed -# Example 12 +print("Example 12") from datetime import timedelta -now_func = Mock(spec=datetime.utcnow) -now_func.return_value = datetime(2019, 6, 5, 15, 45) +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(2019, 6, 5, 11, 15)), - ('Fluffy', datetime(2019, 6, 5, 12, 30)), - ('Jojo', datetime(2019, 6, 5, 12, 45)), + ("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) -# Example 13 +print("Example 13") result = do_rounds( database, - 'Meerkat', + "Meerkat", now_func=now_func, food_func=food_func, animals_func=animals_func, - feed_func=feed_func) + feed_func=feed_func, +) assert result == 2 -# Example 14 +print("Example 14") from unittest.mock import call -food_func.assert_called_once_with(database, 'Meerkat') +food_func.assert_called_once_with(database, "Meerkat") -animals_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), + call(database, "Spot", now_func.return_value), + call(database, "Fluffy", now_func.return_value), ], - any_order=True) + any_order=True, +) +# Make sure these variables don't pollute later tests +del food_func +del animals_func +del feed_func -# Example 15 + +print("Example 15") from unittest.mock import patch -print('Outside patch:', get_animals) +print("Outside patch:", get_animals) -with patch('__main__.get_animals'): - print('Inside patch: ', get_animals) +with patch("__main__.get_animals"): + print("Inside patch: ", get_animals) -print('Outside again:', get_animals) +print("Outside again:", get_animals) -# Example 16 +print("Example 16") try: - fake_now = datetime(2019, 6, 5, 15, 45) + fake_now = datetime(2024, 6, 5, 15, 45) - with patch('datetime.datetime.utcnow'): - datetime.utcnow.return_value = fake_now + with patch("datetime.datetime.now"): + datetime.now.return_value = fake_now except: logging.exception('Expected') else: assert False -# Example 17 +print("Example 17") def get_do_rounds_time(): - return datetime.datetime.utcnow() + return datetime.now() def do_rounds(database, species): now = get_do_rounds_time() -with patch('__main__.get_do_rounds_time'): +with patch("__main__.get_do_rounds_time"): pass -# Example 18 -def do_rounds(database, species, *, utcnow=datetime.utcnow): - now = utcnow() +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 @@ -275,33 +288,36 @@ def do_rounds(database, species, *, utcnow=datetime.utcnow): return fed -# Example 19 +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.utcnow) - now_func.return_value = datetime(2019, 6, 5, 15, 45) +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(2019, 6, 5, 11, 15)), - ('Fluffy', datetime(2019, 6, 5, 12, 30)), - ('Jojo', datetime(2019, 6, 5, 12, 45)) + ("Spot", datetime(2024, 6, 5, 11, 15)), + ("Fluffy", datetime(2024, 6, 5, 12, 30)), + ("Jojo", datetime(2024, 6, 5, 12, 45)), ] -# Example 20 - result = do_rounds(database, 'Meerkat', utcnow=now_func) + 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') + 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), + call(database, "Spot", now_func.return_value), + call(database, "Fluffy", now_func.return_value), ], - any_order=True) + any_order=True, + ) diff --git a/example_code/item_79.py b/example_code/item_112.py similarity index 71% rename from example_code/item_79.py rename to example_code/item_112.py index 1769e1a..c707d69 100755 --- a/example_code/item_79.py +++ b/example_code/item_112.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") class ZooDatabase: def get_animals(self, species): @@ -59,11 +60,11 @@ def feed_animal(self, name, when): pass -# Example 2 +print("Example 2") from datetime import datetime -def do_rounds(database, species, *, utcnow=datetime.utcnow): - now = utcnow() +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 @@ -76,7 +77,7 @@ def do_rounds(database, species, *, utcnow=datetime.utcnow): return fed -# Example 3 +print("Example 3") from unittest.mock import Mock database = Mock(spec=ZooDatabase) @@ -85,37 +86,38 @@ def do_rounds(database, species, *, utcnow=datetime.utcnow): database.feed_animal.assert_any_call() -# Example 4 +print("Example 4") from datetime import timedelta from unittest.mock import call -now_func = Mock(spec=datetime.utcnow) +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)) + ("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) +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.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), + call("Spot", now_func.return_value), + call("Fluffy", now_func.return_value), ], - any_order=True) + any_order=True, +) -# Example 6 +print("Example 6") try: database.bad_method_name() except: @@ -124,7 +126,7 @@ def do_rounds(database, species, *, utcnow=datetime.utcnow): assert False -# Example 7 +print("Example 7") DATABASE = None def get_database(): @@ -137,30 +139,30 @@ def main(argv): database = get_database() species = argv[1] count = do_rounds(database, species) - print(f'Fed {count} {species}(s)') + print(f"Fed {count} {species}(s)") return 0 -# Example 8 +print("Example 8") import contextlib import io from unittest.mock import patch -with patch('__main__.DATABASE', spec=ZooDatabase): - now = datetime.utcnow() +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)), + ("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']) + main(["program name", "Meerkat"]) found = fake_stdout.getvalue() - expected = 'Fed 2 Meerkat(s)\n' + 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_80/debugging/always_breakpoint.py b/example_code/item_114/debugging/always_breakpoint.py similarity index 92% rename from example_code/item_80/debugging/always_breakpoint.py rename to example_code/item_114/debugging/always_breakpoint.py index 582a050..6649805 100755 --- a/example_code/item_80/debugging/always_breakpoint.py +++ b/example_code/item_114/debugging/always_breakpoint.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -31,5 +31,6 @@ def compute_rmse(observed, ideal): result = compute_rmse( [1.8, 1.7, 3.2, 6], - [2, 1.5, 3, 5]) + [2, 1.5, 3, 5], +) print(result) diff --git a/example_code/item_80/debugging/conditional_breakpoint.py b/example_code/item_114/debugging/conditional_breakpoint.py similarity index 92% rename from example_code/item_80/debugging/conditional_breakpoint.py rename to example_code/item_114/debugging/conditional_breakpoint.py index 6540c3f..3a31f38 100755 --- a/example_code/item_80/debugging/conditional_breakpoint.py +++ b/example_code/item_114/debugging/conditional_breakpoint.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ # limitations under the License. import math + def compute_rmse(observed, ideal): total_err_2 = 0 count = 0 @@ -30,5 +31,6 @@ def compute_rmse(observed, ideal): result = compute_rmse( [1.8, 1.7, 3.2, 7], - [2, 1.5, 3, 5]) + [2, 1.5, 3, 5], +) print(result) diff --git a/example_code/item_80/debugging/my_module.py b/example_code/item_114/debugging/my_module.py similarity index 92% rename from example_code/item_80/debugging/my_module.py rename to example_code/item_114/debugging/my_module.py index e22ca05..dd55060 100755 --- a/example_code/item_80/debugging/my_module.py +++ b/example_code/item_114/debugging/my_module.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,9 +16,11 @@ import math + def squared_error(point, mean): err = point - mean - return err ** 2 + return err**2 + def compute_variance(data): mean = sum(data) / len(data) @@ -26,6 +28,7 @@ def compute_variance(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_114/debugging/postmortem_breakpoint.py similarity index 92% rename from example_code/item_80/debugging/postmortem_breakpoint.py rename to example_code/item_114/debugging/postmortem_breakpoint.py index 9a6e365..ab0f35e 100755 --- a/example_code/item_80/debugging/postmortem_breakpoint.py +++ b/example_code/item_114/debugging/postmortem_breakpoint.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -30,5 +30,6 @@ def compute_rmse(observed, ideal): result = compute_rmse( [1.8, 1.7, 3.2, 7j], # Bad input - [2, 1.5, 3, 5]) + [2, 1.5, 3, 5], +) print(result) diff --git a/example_code/item_81/tracemalloc/top_n.py b/example_code/item_115/tracemalloc/top_n.py similarity index 88% rename from example_code/item_81/tracemalloc/top_n.py rename to example_code/item_115/tracemalloc/top_n.py index 8e7e698..8a4abce 100755 --- a/example_code/item_81/tracemalloc/top_n.py +++ b/example_code/item_115/tracemalloc/top_n.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -24,6 +24,6 @@ x = waste_memory.run() # Usage to debug time2 = tracemalloc.take_snapshot() # After snapshot -stats = time2.compare_to(time1, 'lineno') # Compare snapshots +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_115/tracemalloc/using_gc.py similarity index 84% rename from example_code/item_81/tracemalloc/using_gc.py rename to example_code/item_115/tracemalloc/using_gc.py index 1fe0574..39faa18 100755 --- a/example_code/item_81/tracemalloc/using_gc.py +++ b/example_code/item_115/tracemalloc/using_gc.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,15 +17,15 @@ import gc found_objects = gc.get_objects() -print('Before:', len(found_objects)) +print("Before:", len(found_objects)) import waste_memory hold_reference = waste_memory.run() found_objects = gc.get_objects() -print('After: ', len(found_objects)) +print("After: ", len(found_objects)) for obj in found_objects[:3]: print(repr(obj)[:100]) -print('...') +print("...") diff --git a/example_code/item_81/tracemalloc/waste_memory.py b/example_code/item_115/tracemalloc/waste_memory.py similarity index 94% rename from example_code/item_81/tracemalloc/waste_memory.py rename to example_code/item_115/tracemalloc/waste_memory.py index 5afd71b..f545535 100755 --- a/example_code/item_81/tracemalloc/waste_memory.py +++ b/example_code/item_115/tracemalloc/waste_memory.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/example_code/item_81/tracemalloc/with_trace.py b/example_code/item_115/tracemalloc/with_trace.py similarity index 81% rename from example_code/item_81/tracemalloc/with_trace.py rename to example_code/item_115/tracemalloc/with_trace.py index 4f506ee..3e8d7a2 100755 --- a/example_code/item_81/tracemalloc/with_trace.py +++ b/example_code/item_115/tracemalloc/with_trace.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ x = waste_memory.run() time2 = tracemalloc.take_snapshot() -stats = time2.compare_to(time1, 'traceback') +stats = time2.compare_to(time1, "traceback") top = stats[0] -print('Biggest offender is:') -print('\n'.join(top.traceback.format())) +print("Biggest offender is:") +print("\n".join(top.traceback.format())) diff --git a/example_code/item_84.py b/example_code/item_118.py similarity index 86% rename from example_code/item_84.py rename to example_code/item_118.py index f673237..dd27484 100755 --- a/example_code/item_84.py +++ b/example_code/item_118.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,22 +44,23 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +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') +assert palindrome("tacocat") +assert not palindrome("banana") -# Example 2 -print(repr(palindrome.__doc__)) +print("Example 2") +print(palindrome.__doc__) -# Example 3 +print("Example 3") """Library for finding linguistic patterns in words. Testing how words relate to each other can be tricky sometimes! @@ -73,7 +74,7 @@ def palindrome(word): """ -# Example 4 +print("Example 4") class Player: """Represents a player of the game. @@ -87,8 +88,9 @@ class Player: """ -# Example 5 +print("Example 5") import itertools + def find_anagrams(word, dictionary): """Find all anagrams for a word. @@ -105,8 +107,9 @@ def find_anagrams(word, dictionary): none were found. """ permutations = itertools.permutations(word, len(word)) - possible = (''.join(x) for x in permutations) + 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'] + +assert find_anagrams("pancakes", ["scanpeak"]) == ["scanpeak"] diff --git a/example_code/item_90_example_04.py b/example_code/item_118_example_06.py similarity index 69% rename from example_code/item_90_example_04.py rename to example_code/item_118_example_06.py index 6d6fe10..a1b101a 100755 --- a/example_code/item_90_example_04.py +++ b/example_code/item_118_example_06.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,10 +16,10 @@ -# Example 4 -# Check types in this file with: python -m mypy +print("Example 6") +# Check types in this file with: python3 -m mypy -def concat(a: str, b: str) -> str: - return a + b +from collections.abc import Container -concat('first', b'second') # Oops: passed bytes value +def find_anagrams(word: str, dictionary: Container[str]) -> list[str]: + return [] diff --git a/example_code/item_84_example_07.py b/example_code/item_118_example_07.py similarity index 76% rename from example_code/item_84_example_07.py rename to example_code/item_118_example_07.py index e6d4bc2..cfe7598 100755 --- a/example_code/item_84_example_07.py +++ b/example_code/item_118_example_07.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,13 +16,12 @@ -# Example 7 -# Check types in this file with: python -m mypy +print("Example 7") +# Check types in this file with: python3 -m mypy -from typing import Container, List +from collections.abc import Container -def find_anagrams(word: str, - dictionary: Container[str]) -> List[str]: +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 @@ -35,4 +34,4 @@ def find_anagrams(word: str, Returns: Anagrams that were found. """ - pass + return [] diff --git a/example_code/item_85/api_package/api_consumer.py b/example_code/item_119/api_package/api_consumer.py similarity index 93% rename from example_code/item_85/api_package/api_consumer.py rename to example_code/item_119/api_package/api_consumer.py index 46092f7..da151bc 100755 --- a/example_code/item_85/api_package/api_consumer.py +++ b/example_code/item_119/api_package/api_consumer.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ print(after_a.__dict__, after_b.__dict__) import mypackage + try: mypackage._dot_product assert False 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_85/api_package/main.py b/example_code/item_119/api_package/main2.py similarity index 91% rename from example_code/item_85/api_package/main.py rename to example_code/item_119/api_package/main2.py index 636228d..993afef 100755 --- a/example_code/item_85/api_package/main.py +++ b/example_code/item_119/api_package/main2.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/example_code/item_85/api_package/mypackage/__init__.py b/example_code/item_119/api_package/mypackage/__init__.py similarity index 86% rename from example_code/item_85/api_package/mypackage/__init__.py rename to example_code/item_119/api_package/mypackage/__init__.py index 025222d..54e681b 100755 --- a/example_code/item_85/api_package/mypackage/__init__.py +++ b/example_code/item_119/api_package/mypackage/__init__.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,9 @@ # limitations under the License. __all__ = [] -from . models import * +from .models import * + __all__ += models.__all__ -from . utils import * +from .utils import * + __all__ += utils.__all__ diff --git a/example_code/item_85/api_package/mypackage/models.py b/example_code/item_119/api_package/mypackage/models.py similarity index 89% rename from example_code/item_85/api_package/mypackage/models.py rename to example_code/item_119/api_package/mypackage/models.py index b72c3c6..5b7b7ff 100755 --- a/example_code/item_85/api_package/mypackage/models.py +++ b/example_code/item_119/api_package/mypackage/models.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -__all__ = ['Projectile'] +__all__ = ["Projectile"] class Projectile: def __init__(self, mass, velocity): diff --git a/example_code/item_85/api_package/mypackage/utils.py b/example_code/item_119/api_package/mypackage/utils.py similarity index 86% rename from example_code/item_85/api_package/mypackage/utils.py rename to example_code/item_119/api_package/mypackage/utils.py index fc37afa..327e0cd 100755 --- a/example_code/item_85/api_package/mypackage/utils.py +++ b/example_code/item_119/api_package/mypackage/utils.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,9 +14,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from . models import Projectile +from .models import Projectile -__all__ = ['simulate_collision'] +__all__ = ["simulate_collision"] def _dot_product(a, b): pass diff --git a/example_code/item_85/namespace_package/analysis/__init__.py b/example_code/item_119/namespace_package/analysis/__init__.py similarity index 90% rename from example_code/item_85/namespace_package/analysis/__init__.py rename to example_code/item_119/namespace_package/analysis/__init__.py index 18e252f..bb03669 100755 --- a/example_code/item_85/namespace_package/analysis/__init__.py +++ b/example_code/item_119/namespace_package/analysis/__init__.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/example_code/item_85/namespace_package/analysis/utils.py b/example_code/item_119/namespace_package/analysis/utils.py similarity index 91% rename from example_code/item_85/namespace_package/analysis/utils.py rename to example_code/item_119/namespace_package/analysis/utils.py index 972a3fb..a89126d 100755 --- a/example_code/item_85/namespace_package/analysis/utils.py +++ b/example_code/item_119/namespace_package/analysis/utils.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,8 +15,13 @@ # 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_119/namespace_package/frontend/__init__.py similarity index 90% rename from example_code/item_85/namespace_package/frontend/__init__.py rename to example_code/item_119/namespace_package/frontend/__init__.py index 18e252f..bb03669 100755 --- a/example_code/item_85/namespace_package/frontend/__init__.py +++ b/example_code/item_119/namespace_package/frontend/__init__.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/example_code/item_85/namespace_package/frontend/utils.py b/example_code/item_119/namespace_package/frontend/utils.py similarity index 91% rename from example_code/item_85/namespace_package/frontend/utils.py rename to example_code/item_119/namespace_package/frontend/utils.py index 69ed8a7..0419e89 100755 --- a/example_code/item_85/namespace_package/frontend/utils.py +++ b/example_code/item_119/namespace_package/frontend/utils.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,5 +17,8 @@ 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_119/namespace_package/main.py similarity index 92% rename from example_code/item_85/namespace_package/main.py rename to example_code/item_119/namespace_package/main.py index 4bbf563..4963b4e 100755 --- a/example_code/item_85/namespace_package/main.py +++ b/example_code/item_119/namespace_package/main.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/example_code/item_85/namespace_package/main2.py b/example_code/item_119/namespace_package/main2.py similarity index 88% rename from example_code/item_85/namespace_package/main2.py rename to example_code/item_119/namespace_package/main2.py index 8b4bb85..9071f42 100755 --- a/example_code/item_85/namespace_package/main2.py +++ b/example_code/item_119/namespace_package/main2.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,5 +16,6 @@ from analysis.utils import inspect from frontend.utils import inspect # Overwrites! -'frontend' in inspect.__module__ + +"frontend" in inspect.__module__ print(inspect.__module__) diff --git a/example_code/item_85/namespace_package/main3.py b/example_code/item_119/namespace_package/main3.py similarity index 89% rename from example_code/item_85/namespace_package/main3.py rename to example_code/item_119/namespace_package/main3.py index e013b73..41b2380 100755 --- a/example_code/item_85/namespace_package/main3.py +++ b/example_code/item_119/namespace_package/main3.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,4 +19,4 @@ value = 33 if analysis_inspect(value) == frontend_inspect(value): - print('Inspection equal!') + print("Inspection equal!") diff --git a/example_code/item_85/namespace_package/main4.py b/example_code/item_119/namespace_package/main4.py similarity index 79% rename from example_code/item_85/namespace_package/main4.py rename to example_code/item_119/namespace_package/main4.py index 0dfb064..99b16f8 100755 --- a/example_code/item_85/namespace_package/main4.py +++ b/example_code/item_119/namespace_package/main4.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,6 +18,5 @@ import frontend.utils value = 33 -if (analysis.utils.inspect(value) == - frontend.utils.inspect(value)): - print('Inspection equal!') +if analysis.utils.inspect(value) == frontend.utils.inspect(value): + print("Inspection equal!") diff --git a/example_code/item_86.py b/example_code/item_120.py similarity index 88% rename from example_code/item_86.py rename to example_code/item_120.py index 492fa14..f4e6da6 100755 --- a/example_code/item_86.py +++ b/example_code/item_120.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,9 +44,10 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 4 +print("Example 4") # db_connection.py import sys @@ -56,7 +57,7 @@ class Win32Database: class PosixDatabase: pass -if sys.platform.startswith('win32'): +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_120/module_scope/db_connection.py similarity index 92% rename from example_code/item_86/module_scope/db_connection.py rename to example_code/item_120/module_scope/db_connection.py index df269db..6db2ac5 100755 --- a/example_code/item_86/module_scope/db_connection.py +++ b/example_code/item_120/module_scope/db_connection.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/example_code/item_86/module_scope/dev_main.py b/example_code/item_120/module_scope/dev_main.py similarity index 91% rename from example_code/item_86/module_scope/dev_main.py rename to example_code/item_120/module_scope/dev_main.py index cfcb4c5..2044a89 100755 --- a/example_code/item_86/module_scope/dev_main.py +++ b/example_code/item_120/module_scope/dev_main.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/example_code/item_86/module_scope/prod_main.py b/example_code/item_120/module_scope/prod_main.py similarity index 91% rename from example_code/item_86/module_scope/prod_main.py rename to example_code/item_120/module_scope/prod_main.py index 3dda7da..669078c 100755 --- a/example_code/item_86/module_scope/prod_main.py +++ b/example_code/item_120/module_scope/prod_main.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/example_code/item_87.py b/example_code/item_121.py similarity index 78% rename from example_code/item_87.py rename to example_code/item_121.py index 07c20c9..4d9f308 100755 --- a/example_code/item_87.py +++ b/example_code/item_121.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,13 +44,15 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") # my_module.py def determine_weight(volume, density): if density <= 0: - raise ValueError('Density must be positive') + raise ValueError("Density must be positive") + try: determine_weight(1, 0) @@ -60,7 +62,7 @@ def determine_weight(volume, density): assert False -# Example 2 +print("Example 2") # my_module.py class Error(Exception): """Base-class for all exceptions raised by this module.""" @@ -73,14 +75,14 @@ class InvalidVolumeError(Error): def determine_weight(volume, density): if density < 0: - raise InvalidDensityError('Density must be positive') + raise InvalidDensityError("Density must be positive") if volume < 0: - raise InvalidVolumeError('Volume must be positive') + raise InvalidVolumeError("Volume must be positive") if volume == 0: density / volume -# Example 3 +print("Example 3") class my_module: Error = Error InvalidDensityError = InvalidDensityError @@ -88,21 +90,21 @@ class my_module: @staticmethod def determine_weight(volume, density): if density < 0: - raise InvalidDensityError('Density must be positive') + raise InvalidDensityError("Density must be positive") if volume < 0: - raise InvalidVolumeError('Volume must be positive') + 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') + logging.exception("Unexpected error") else: assert False -# Example 4 +print("Example 4") SENTINEL = object() weight = SENTINEL try: @@ -110,14 +112,14 @@ def determine_weight(volume, density): except my_module.InvalidDensityError: weight = 0 except my_module.Error: - logging.exception('Bug in the calling code') + logging.exception("Bug in the calling code") else: assert False assert weight is SENTINEL -# Example 5 +print("Example 5") try: weight = SENTINEL try: @@ -125,9 +127,9 @@ def determine_weight(volume, density): except my_module.InvalidDensityError: weight = 0 except my_module.Error: - logging.exception('Bug in the calling code') + logging.exception("Bug in the calling code") except Exception: - logging.exception('Bug in the API code!') + logging.exception("Bug in the API code!") raise # Re-raise exception to the caller else: assert False @@ -139,7 +141,7 @@ def determine_weight(volume, density): assert False -# Example 6 +print("Example 6") # my_module.py class NegativeDensityError(InvalidDensityError): @@ -148,23 +150,23 @@ class NegativeDensityError(InvalidDensityError): def determine_weight(volume, density): if density < 0: - raise NegativeDensityError('Density must be positive') + raise NegativeDensityError("Density must be positive") -# Example 7 +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') + raise ValueError("Must supply non-negative density") except my_module.InvalidDensityError: weight = 0 except my_module.Error: - logging.exception('Bug in the calling code') + logging.exception("Bug in the calling code") except Exception: - logging.exception('Bug in the API code!') + logging.exception("Bug in the API code!") raise else: assert False @@ -174,7 +176,7 @@ def determine_weight(volume, density): assert False -# Example 8 +print("Example 8") # my_module.py class Error(Exception): """Base-class for all exceptions raised by this module.""" diff --git a/example_code/item_88/recursive_import_dynamic/app.py b/example_code/item_122/recursive_import_bad/app.py similarity index 91% rename from example_code/item_88/recursive_import_dynamic/app.py rename to example_code/item_122/recursive_import_bad/app.py index cac1ea8..b0dd91e 100755 --- a/example_code/item_88/recursive_import_dynamic/app.py +++ b/example_code/item_122/recursive_import_bad/app.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/example_code/item_88/recursive_import_bad/dialog.py b/example_code/item_122/recursive_import_bad/dialog.py similarity index 83% rename from example_code/item_88/recursive_import_bad/dialog.py rename to example_code/item_122/recursive_import_bad/dialog.py index 44aa232..b98cc57 100755 --- a/example_code/item_88/recursive_import_bad/dialog.py +++ b/example_code/item_122/recursive_import_bad/dialog.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,7 +20,8 @@ class Dialog: def __init__(self, save_dir): self.save_dir = save_dir -save_dialog = Dialog(app.prefs.get('save_dir')) + +save_dialog = Dialog(app.prefs.get("save_dir")) def show(): - print('Showing the dialog!') + print("Showing the dialog!") diff --git a/example_code/item_88/recursive_import_bad/main.py b/example_code/item_122/recursive_import_bad/main.py similarity index 90% rename from example_code/item_88/recursive_import_bad/main.py rename to example_code/item_122/recursive_import_bad/main.py index eeeba96..bf57d21 100755 --- a/example_code/item_88/recursive_import_bad/main.py +++ b/example_code/item_122/recursive_import_bad/main.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/example_code/item_88/recursive_import_bad/app.py b/example_code/item_122/recursive_import_dynamic/app.py similarity index 91% rename from example_code/item_88/recursive_import_bad/app.py rename to example_code/item_122/recursive_import_dynamic/app.py index cac1ea8..46aca3d 100755 --- a/example_code/item_88/recursive_import_bad/app.py +++ b/example_code/item_122/recursive_import_dynamic/app.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,5 +20,6 @@ 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_122/recursive_import_dynamic/dialog.py similarity index 85% rename from example_code/item_88/recursive_import_dynamic/dialog.py rename to example_code/item_122/recursive_import_dynamic/dialog.py index de500cf..2824cea 100755 --- a/example_code/item_88/recursive_import_dynamic/dialog.py +++ b/example_code/item_122/recursive_import_dynamic/dialog.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,11 +21,13 @@ 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!') + + save_dialog.save_dir = app.prefs.get("save_dir") + print("Showing the dialog!") diff --git a/example_code/item_88/recursive_import_ordering/main.py b/example_code/item_122/recursive_import_dynamic/main.py similarity index 90% rename from example_code/item_88/recursive_import_ordering/main.py rename to example_code/item_122/recursive_import_dynamic/main.py index eeeba96..bf57d21 100755 --- a/example_code/item_88/recursive_import_ordering/main.py +++ b/example_code/item_122/recursive_import_dynamic/main.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/example_code/item_88/recursive_import_nosideeffects/app.py b/example_code/item_122/recursive_import_nosideeffects/app.py similarity index 91% rename from example_code/item_88/recursive_import_nosideeffects/app.py rename to example_code/item_122/recursive_import_nosideeffects/app.py index 7cd5bc0..10734f2 100755 --- a/example_code/item_88/recursive_import_nosideeffects/app.py +++ b/example_code/item_122/recursive_import_nosideeffects/app.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ class Prefs: def get(self, name): pass + prefs = Prefs() def configure(): diff --git a/example_code/item_88/recursive_import_nosideeffects/dialog.py b/example_code/item_122/recursive_import_nosideeffects/dialog.py similarity index 82% rename from example_code/item_88/recursive_import_nosideeffects/dialog.py rename to example_code/item_122/recursive_import_nosideeffects/dialog.py index 9c5b772..0138142 100755 --- a/example_code/item_88/recursive_import_nosideeffects/dialog.py +++ b/example_code/item_122/recursive_import_nosideeffects/dialog.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,10 +20,11 @@ class Dialog: def __init__(self): pass + save_dialog = Dialog() def show(): - print('Showing the dialog!') + print("Showing the dialog!") def configure(): - save_dialog.save_dir = app.prefs.get('save_dir') + 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_122/recursive_import_nosideeffects/main.py similarity index 91% rename from example_code/item_88/recursive_import_nosideeffects/main.py rename to example_code/item_122/recursive_import_nosideeffects/main.py index 4bcbbff..7a39f76 100755 --- a/example_code/item_88/recursive_import_nosideeffects/main.py +++ b/example_code/item_122/recursive_import_nosideeffects/main.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/example_code/item_88/recursive_import_ordering/app.py b/example_code/item_122/recursive_import_ordering/app.py similarity index 91% rename from example_code/item_88/recursive_import_ordering/app.py rename to example_code/item_122/recursive_import_ordering/app.py index c5abf30..12b9e5f 100755 --- a/example_code/item_88/recursive_import_ordering/app.py +++ b/example_code/item_122/recursive_import_ordering/app.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,9 @@ 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_122/recursive_import_ordering/dialog.py similarity index 82% rename from example_code/item_88/recursive_import_ordering/dialog.py rename to example_code/item_122/recursive_import_ordering/dialog.py index 44aa232..f36cb22 100755 --- a/example_code/item_88/recursive_import_ordering/dialog.py +++ b/example_code/item_122/recursive_import_ordering/dialog.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,11 +16,15 @@ import app + class Dialog: def __init__(self, save_dir): self.save_dir = save_dir -save_dialog = Dialog(app.prefs.get('save_dir')) + + +save_dialog = Dialog(app.prefs.get("save_dir")) + def show(): - print('Showing the dialog!') + print("Showing the dialog!") diff --git a/example_code/item_88/recursive_import_dynamic/main.py b/example_code/item_122/recursive_import_ordering/main.py similarity index 90% rename from example_code/item_88/recursive_import_dynamic/main.py rename to example_code/item_122/recursive_import_ordering/main.py index eeeba96..bf57d21 100755 --- a/example_code/item_88/recursive_import_dynamic/main.py +++ b/example_code/item_122/recursive_import_ordering/main.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/example_code/item_89.py b/example_code/item_123.py similarity index 56% rename from example_code/item_89.py rename to example_code/item_123.py index e9fe12a..be85f79 100755 --- a/example_code/item_89.py +++ b/example_code/item_123.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,28 +44,29 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") def print_distance(speed, duration): distance = speed * duration - print(f'{distance} miles') + print(f"{distance} miles") print_distance(5, 2.5) -# Example 2 +print("Example 2") print_distance(1000, 3) -# Example 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 - 'seconds': 1, # s + "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): @@ -76,109 +77,150 @@ def localize(value, units): rate = CONVERSIONS[units] return value / rate -def print_distance(speed, duration, *, - speed_units='mph', - time_units='hours', - distance_units='miles'): +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(f"{distance} {distance_units}") -# Example 4 -print_distance(1000, 3, - speed_units='meters', - time_units='seconds') +print("Example 4") +print_distance( + 1000, + 3, + speed_units="meters", + time_units="seconds", +) -# Example 5 +print("Example 5") import warnings -def print_distance(speed, duration, *, - speed_units=None, - time_units=None, - distance_units=None): +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' + "speed_units required", + DeprecationWarning, + ) + speed_units = "mph" if time_units is None: warnings.warn( - 'time_units required', DeprecationWarning) - time_units = 'hours' + "time_units required", + DeprecationWarning, + ) + time_units = "hours" if distance_units is None: warnings.warn( - 'distance_units required', DeprecationWarning) - distance_units = 'miles' + "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(f"{distance} {distance_units}") -# Example 6 +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_distance( + 1000, + 3, + speed_units="meters", + time_units="seconds", + ) print(fake_stderr.getvalue()) -# Example 7 +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', + f"{name} will be required soon, update your code", DeprecationWarning, - stacklevel=3) + 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') +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') + "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(f"{distance} {distance_units}") -# Example 8 +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_distance( + 1000, + 3, + speed_units="meters", + time_units="seconds", + ) print(fake_stderr.getvalue()) -# Example 9 -warnings.simplefilter('error') +print("Example 9") +warnings.simplefilter("error") try: - warnings.warn('This usage is deprecated', - DeprecationWarning) + warnings.warn( + "This usage is deprecated", + DeprecationWarning, + ) except DeprecationWarning: pass # Expected else: @@ -187,48 +229,48 @@ def print_distance(speed, duration, *, warnings.resetwarnings() -# Example 10 +print("Example 10") warnings.resetwarnings() -warnings.simplefilter('ignore') -warnings.warn('This will not be printed to stderr') +warnings.simplefilter("ignore") +warnings.warn("This will not be printed to stderr") warnings.resetwarnings() -# Example 11 +print("Example 11") import logging fake_stderr = io.StringIO() handler = logging.StreamHandler(fake_stderr) -formatter = logging.Formatter( - '%(asctime)-15s WARNING] %(message)s') +formatter = logging.Formatter("%(asctime)-15s WARNING] %(message)s") handler.setFormatter(formatter) logging.captureWarnings(True) -logger = logging.getLogger('py.warnings') +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') +warnings.simplefilter("default") +warnings.warn("This will go to the logs output") print(fake_stderr.getvalue()) warnings.resetwarnings() -# Example 12 +print("Example 12") with warnings.catch_warnings(record=True) as found_warnings: - found = require('my_arg', None, 'fake units') - expected = 'fake units' + found = require("my_arg", None, "fake units") + expected = "fake units" assert found == expected -# Example 13 +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') + "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_124.py similarity index 86% rename from example_code/item_90.py rename to example_code/item_124.py index 792a8c7..7510fda 100755 --- a/example_code/item_90.py +++ b/example_code/item_124.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Reproduce book environment +### Start book environment setup import random random.seed(1234) @@ -44,33 +44,22 @@ def close_open_files(): obj.close() atexit.register(close_open_files) +### End book environment setup -# Example 1 +print("Example 1") try: def subtract(a, b): return a - b - subtract(10, '5') + 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 +print("Example 3") class Counter: def __init__(self): self.value = 0 @@ -82,7 +71,7 @@ def get(self) -> int: self.value -# Example 6 +print("Example 4") try: counter = Counter() counter.add(5) @@ -92,7 +81,7 @@ def get(self) -> int: assert False -# Example 7 +print("Example 5") try: counter = Counter() found = counter.get() @@ -103,7 +92,7 @@ def get(self) -> int: assert False -# Example 9 +print("Example 7") try: def combine(func, values): assert len(values) > 0 @@ -126,9 +115,9 @@ def add(x, y): assert False -# Example 11 +print("Example 9") try: - def get_or_default(value, default): + def get_or_default(value, default): if value is not None: return value return value @@ -144,7 +133,7 @@ def get_or_default(value, default): assert False -# Example 13 +print("Example 11") class FirstClass: def __init__(self, value): self.value = value @@ -160,7 +149,7 @@ def __init__(self, value): del SecondClass -# Example 15 +print("Example 13") try: class FirstClass: def __init__(self, value: SecondClass) -> None: # Breaks @@ -178,9 +167,9 @@ def __init__(self, value: int) -> None: assert False -# Example 16 +print("Example 14") class FirstClass: - def __init__(self, value: 'SecondClass') -> None: # OK + def __init__(self, value: "SecondClass") -> None: # OK self.value = value class SecondClass: diff --git a/example_code/item_90_example_02.py b/example_code/item_124_example_02.py similarity index 78% rename from example_code/item_90_example_02.py rename to example_code/item_124_example_02.py index 012cffa..5152d85 100755 --- a/example_code/item_90_example_02.py +++ b/example_code/item_124_example_02.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,10 +16,10 @@ -# Example 2 -# Check types in this file with: python -m mypy +print("Example 2") +# Check types in this file with: python3 -m mypy def subtract(a: int, b: int) -> int: # Function annotation return a - b -subtract(10, '5') # Oops: passed string value +subtract(10, "5") # Oops: passed string value diff --git a/example_code/item_90_example_08.py b/example_code/item_124_example_06.py similarity index 87% rename from example_code/item_90_example_08.py rename to example_code/item_124_example_06.py index 09f42e6..9dd5763 100755 --- a/example_code/item_90_example_08.py +++ b/example_code/item_124_example_06.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ -# Example 8 -# Check types in this file with: python -m mypy +print("Example 6") +# Check types in this file with: python3 -m mypy class Counter: def __init__(self) -> None: diff --git a/example_code/item_90_example_10.py b/example_code/item_124_example_08.py similarity index 74% rename from example_code/item_90_example_10.py rename to example_code/item_124_example_08.py index 75ee1ca..d27afe6 100755 --- a/example_code/item_90_example_10.py +++ b/example_code/item_124_example_08.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,15 +16,16 @@ -# Example 10 -# Check types in this file with: python -m mypy +print("Example 8") +# Check types in this file with: python3 -m mypy -from typing import Callable, List, TypeVar +from collections.abc import Callable +from typing import TypeVar -Value = TypeVar('Value') +Value = TypeVar("Value") Func = Callable[[Value, Value], Value] -def combine(func: Func[Value], values: List[Value]) -> Value: +def combine(func: Func[Value], values: list[Value]) -> Value: assert len(values) > 0 result = values[0] @@ -33,7 +34,7 @@ def combine(func: Func[Value], values: List[Value]) -> Value: return result -Real = TypeVar('Real', int, float) +Real = TypeVar("Real", int, float) def add(x: Real, y: Real) -> Real: return x + y diff --git a/example_code/item_90_example_12.py b/example_code/item_124_example_10.py similarity index 74% rename from example_code/item_90_example_12.py rename to example_code/item_124_example_10.py index b0d98b9..15a9efb 100755 --- a/example_code/item_90_example_12.py +++ b/example_code/item_124_example_10.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,13 +16,10 @@ -# Example 12 -# Check types in this file with: python -m mypy +print("Example 10") +# Check types in this file with: python3 -m mypy -from typing import Optional - -def get_or_default(value: Optional[int], - default: int) -> int: +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_90_example_14.py b/example_code/item_124_example_12.py similarity index 85% rename from example_code/item_90_example_14.py rename to example_code/item_124_example_12.py index 1feacb8..78fbfed 100755 --- a/example_code/item_90_example_14.py +++ b/example_code/item_124_example_12.py @@ -1,6 +1,6 @@ #!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014-2019 Brett Slatkin, Pearson Education Inc. +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ -# Example 14 -# Check types in this file with: python -m mypy +print("Example 12") +# Check types in this file with: python3 -m mypy class FirstClass: def __init__(self, value: SecondClass) -> None: 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) 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_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)