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 a9eec2a..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_](http://www.effectivepython.com) by looking at the `Confirmed` label for open issues [following this link](https://github.com/bslatkin/effectivepython/issues?q=is%3Aopen+is%3Aissue+label%3AConfirmed). If you found a mistake and don't see it listed there, [please create a new issue](https://github.com/bslatkin/effectivepython/issues/new). Thanks for your help! +You can see a list of errors that have been found in [_Effective Python: 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 668dc6b..338101e 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # Effective Python -Hello! You've reached the official source code repository for _Effective Python_. To learn more about the book or contact the author, please [visit the official website](http://www.effectivepython.com). +Hello! You've reached the official source code repository for _Effective Python: Third Edition_. To learn more about the book or contact the author, please [visit the official website](https://effectivepython.com). -[![Cover](./cover.jpg)](http://www.effectivepython.com) +[![Cover](./cover.jpg)](https://effectivepython.com) In this repository you can browse all of the source code included in the book. Each item has its own file or directory containing the example code. Each file is annotated with which example snippet it came from within each chapter. -To run all the code for an item, just type `./item_01.py` into your shell and see what it prints out. Alternatively you can type `python3 item_01.py` or `python2.7 item_03_example_03.py` to run a specific version of Python. +To run all the code for an item, just type `./item_01.py` into your shell and see what it prints out. Alternatively you can type `python3 item_01.py` to run a specific version of Python. To report a problem with the book or view known issues, please [visit the Errata page](./Errata.md). diff --git a/VIDEO.md b/VIDEO.md index 50130ed..1e902b8 100644 --- a/VIDEO.md +++ b/VIDEO.md @@ -1,38 +1,5 @@ # Example code for [Effective Python LiveLessons](http://www.informit.com/store/effective-python-livelessons-video-training-downloadable-9780134175164) -These items are numbered differently than in the book. Below are links to each item's corresponding piece of example code. +**This is for a previous edition of the book.** -To run all the code for an item, just type ./item_01.py into your shell and see what it prints out. Alternatively you can type python3 item_01.py or python2.7 item_03_example_03.py to run a specific version of Python. - -- "[Item 1 Know how to slice sequences](example_code/item_05.py)" -- "[Item 2 Avoid using start, end, and stride in a single slice](example_code/item_05.py)" -- "[Item 3 Prefer enumerate over range](example_code/item_10.py)" -- "[Item 4 Use zip to process iterators in parallel](example_code/item_11.py)" -- "[Item 5 Avoid else blocks after for and while loops](example_code/item_12.py)" -- "[Item 6 Take advantage of each block in try/except/else/finally](example_code/item_13.py)" -- "[Item 7 Consider contextlib and with statements for reusable try/finally behavior](example_code/item_43.py)" -- "[Item 8 Use list comprehensions instead of map and filter](example_code/item_07.py)" -- "[Item 9 Avoid more than two expressions in list comprehensions](example_code/item_08.py)" -- "[Item 10 Consider generator expressions for large comprehensions](example_code/item_09.py)" -- "[Item 11 Consider generators instead of returning lists](example_code/item_16.py)" -- "[Item 12 Be defensive when iterating over arguments ](example_code/item_17.py)" -- "[Item 13 Know how closures interact with variable scope](example_code/item_15.py)" and [example for Python 2](example_code/item_15_example_09.py) -- "[Item 14 Accept functions for simple interfaces instead of classes](example_code/item_23.py)" -- "[Item 15 Reduce visual noise with variable positional arguments](example_code/item_19.py)" -- "[Item 16 Provide optional behavior with keyword arguments](example_code/item_19.py)" -- "[Item 17 Enforce clarity with keyword-only arguments](example_code/item_21.py)" -- "[Item 18 Use None and docstrings to specify dynamic default arguments](example_code/item_20.py)" -- "[Item 19 Prefer helper classes over bookkeeping with dictionaries and tuples](example_code/item_22.py)" -- "[Item 20 Use plain attributes instead of get and set methods ](example_code/item_29.py)" -- "[Item 21 Prefer public attributes over private ones](example_code/item_27.py)" -- "[Item 22 Use @classmethod polymorphism to construct objects generically](example_code/item_24.py)" -- "[Item 23 Use subprocess to manage child processes](example_code/item_36.py)" -- "[Item 24 Use threads for blocking I/O, avoid for parallelism](example_code/item_37.py)" -- "[Item 25 Use Lock to prevent data races in threads](example_code/item_38.py)" -- "[Item 26 Use Queue to coordinate work between threads](example_code/item_39.py)" -- "[Item 27 Consider concurrent.futures for true parallelism](example_code/item_41.py)" -- "Item 28 Use virtual environments for isolated and reproducible dependencies" has no example code -- "[Item 29 Test everything with unittest](example_code/item_56.py) and [a directory of tests](example_code/item_56/testing)" -- "Item 30 Consider interactive debugging with pdb" has no example code -- "[Item 31 Profile before optimizing](example_code/item_58.py)" -- "[Item 32 Use tracemalloc to understand memory usage and leaks](example_code/item_59/tracemalloc)" +[Go here to see the corresponding example code](../v1/VIDEO.md) diff --git a/cover.jpg b/cover.jpg index f27ca61..b62bd02 100644 Binary files a/cover.jpg and b/cover.jpg differ diff --git a/example_code/.gitignore b/example_code/.gitignore deleted file mode 100644 index b8b2cb7..0000000 --- a/example_code/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.txt -*.bin -*.json diff --git a/example_code/Makefile b/example_code/Makefile deleted file mode 100644 index 722775a..0000000 --- a/example_code/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -test: - for path in $$(ls ./*.py); \ - do \ - echo "\n\nRunning $$path"; \ - $$path 0 y): + pass + + +print("Example 12") +try: + # This will not compile + source = """if x and z := x > y: + pass""" + eval(source) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 13") +if x > y if z else w: # Ambiguous + pass + +if x > (y if z else w): # Clear + pass + + +print("Example 14") +z = dict( + your_value=(y := 1), +) + + +print("Example 15") +try: + # This will not compile + source = """w = dict( + other_value=y := 1, + ) """ + eval(source) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 16") +v = dict( + my_value=1 if x else 3, +) diff --git a/example_code/item_008.py b/example_code/item_008.py new file mode 100755 index 0000000..b4ac6da --- /dev/null +++ b/example_code/item_008.py @@ -0,0 +1,242 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +fresh_fruit = { + "apple": 10, + "banana": 8, + "lemon": 5, +} + + +print("Example 2") +def make_lemonade(count): + print(f"Making {count} lemons into lemonade") + +def out_of_stock(): + print("Out of stock!") + +count = fresh_fruit.get("lemon", 0) +if count: + make_lemonade(count) +else: + out_of_stock() + + +print("Example 3") +if count := fresh_fruit.get("lemon", 0): + make_lemonade(count) +else: + out_of_stock() + + +print("Example 4") +def make_cider(count): + print(f"Making cider with {count} apples") + +count = fresh_fruit.get("apple", 0) +if count >= 4: + make_cider(count) +else: + out_of_stock() + + +print("Example 5") +if (count := fresh_fruit.get("apple", 0)) >= 4: + make_cider(count) +else: + out_of_stock() + + +print("Example 6") +def slice_bananas(count): + print(f"Slicing {count} bananas") + return count * 4 + +class OutOfBananas(Exception): + pass + +def make_smoothies(count): + print(f"Making smoothies with {count} banana slices") + +pieces = 0 +count = fresh_fruit.get("banana", 0) +if count >= 2: + pieces = slice_bananas(count) + +try: + smoothies = make_smoothies(pieces) +except OutOfBananas: + out_of_stock() + + +print("Example 7") +count = fresh_fruit.get("banana", 0) +if count >= 2: + pieces = slice_bananas(count) +else: + pieces = 0 # Moved + +try: + smoothies = make_smoothies(pieces) +except OutOfBananas: + out_of_stock() + + +print("Example 8") +pieces = 0 +if (count := fresh_fruit.get("banana", 0)) >= 2: # Changed + pieces = slice_bananas(count) + +try: + smoothies = make_smoothies(pieces) +except OutOfBananas: + out_of_stock() + + +print("Example 9") +if (count := fresh_fruit.get("banana", 0)) >= 2: + pieces = slice_bananas(count) +else: + pieces = 0 # Moved + +try: + smoothies = make_smoothies(pieces) +except OutOfBananas: + out_of_stock() + + +print("Example 10") +count = fresh_fruit.get("banana", 0) +if count >= 2: + pieces = slice_bananas(count) + to_enjoy = make_smoothies(pieces) +else: + count = fresh_fruit.get("apple", 0) + if count >= 4: + to_enjoy = make_cider(count) + else: + count = fresh_fruit.get("lemon", 0) + if count: + to_enjoy = make_lemonade(count) + else: + to_enjoy = "Nothing" + + +print("Example 11") +if (count := fresh_fruit.get("banana", 0)) >= 2: + pieces = slice_bananas(count) + to_enjoy = make_smoothies(pieces) +elif (count := fresh_fruit.get("apple", 0)) >= 4: + to_enjoy = make_cider(count) +elif count := fresh_fruit.get("lemon", 0): + to_enjoy = make_lemonade(count) +else: + to_enjoy = "Nothing" + + +print("Example 12") +FRUIT_TO_PICK = [ + {"apple": 1, "banana": 3}, + {"lemon": 2, "lime": 5}, + {"orange": 3, "melon": 2}, +] + +def pick_fruit(): + if FRUIT_TO_PICK: + return FRUIT_TO_PICK.pop(0) + else: + return [] + +def make_juice(fruit, count): + return [(fruit, count)] + +bottles = [] +fresh_fruit = pick_fruit() +while fresh_fruit: + for fruit, count in fresh_fruit.items(): + batch = make_juice(fruit, count) + bottles.extend(batch) + fresh_fruit = pick_fruit() + +print(bottles) + + +print("Example 13") +FRUIT_TO_PICK = [ + {"apple": 1, "banana": 3}, + {"lemon": 2, "lime": 5}, + {"orange": 3, "melon": 2}, +] +bottles = [] +while True: # Loop + fresh_fruit = pick_fruit() + if not fresh_fruit: # And a half + break + for fruit, count in fresh_fruit.items(): + batch = make_juice(fruit, count) + bottles.extend(batch) + +print(bottles) + + +print("Example 14") +FRUIT_TO_PICK = [ + {"apple": 1, "banana": 3}, + {"lemon": 2, "lime": 5}, + {"orange": 3, "melon": 2}, +] + +bottles = [] +while fresh_fruit := pick_fruit(): # Changed + for fruit, count in fresh_fruit.items(): + batch = make_juice(fruit, count) + bottles.extend(batch) + +print(bottles) diff --git a/example_code/item_009.py b/example_code/item_009.py new file mode 100755 index 0000000..dc5c1d6 --- /dev/null +++ b/example_code/item_009.py @@ -0,0 +1,311 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def take_action(light): + if light == "red": + print("Stop") + elif light == "yellow": + print("Slow down") + elif light == "green": + print("Go!") + else: + raise RuntimeError + + +print("Example 2") +take_action("red") +take_action("yellow") +take_action("green") + + +print("Example 3") +def take_match_action(light): + match light: + case "red": + print("Stop") + case "yellow": + print("Slow down") + case "green": + print("Go!") + case _: + raise RuntimeError + + +take_match_action("red") +take_match_action("yellow") +take_match_action("green") + + +print("Example 4") +try: + # This will not compile + source = """# Added these constants + RED = "red" + YELLOW = "yellow" + GREEN = "green" + + def take_constant_action(light): + match light: + case RED: # Changed + print("Stop") + case YELLOW: # Changed + print("Slow down") + case GREEN: # Changed + print("Go!") + case _: + raise RuntimeError""" + eval(source) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +RED = "red" +YELLOW = "yellow" +GREEN = "green" + +def take_truncated_action(light): + match light: + case RED: + print("Stop") + + +print("Example 6") +take_truncated_action(GREEN) + + +print("Example 7") +def take_debug_action(light): + match light: + case RED: + print(f"{RED=}, {light=}") + +take_debug_action(GREEN) + + +print("Example 8") +def take_unpacking_action(light): + try: + (RED,) = (light,) + except TypeError: + # Did not match + pass + else: + # Matched + print(f"{RED=}, {light=}") + + +take_unpacking_action(GREEN) + + +print("Example 9") +import enum # Added + +class ColorEnum(enum.Enum): # Added + RED = "red" + YELLOW = "yellow" + GREEN = "green" + +def take_enum_action(light): + match light: + case ColorEnum.RED: # Changed + print("Stop") + case ColorEnum.YELLOW: # Changed + print("Slow down") + case ColorEnum.GREEN: # Changed + print("Go!") + case _: + raise RuntimeError + +take_enum_action(ColorEnum.RED) +take_enum_action(ColorEnum.YELLOW) +take_enum_action(ColorEnum.GREEN) + + +print("Example 10") +for index, value in enumerate("abc"): + print(f"index {index} is {value}") + + +print("Example 11") +my_tree = (10, (7, None, 9), (13, 11, None)) + + +print("Example 12") +def contains(tree, value): + if not isinstance(tree, tuple): + return tree == value + + pivot, left, right = tree + + if value < pivot: + return contains(left, value) + elif value > pivot: + return contains(right, value) + else: + return value == pivot + + +print("Example 13") +assert contains(my_tree, 9) +assert not contains(my_tree, 14) + +for i in range(0, 14): + print(i, contains(my_tree, i)) + + +print("Example 14") +def contains_match(tree, value): + match tree: + case pivot, left, _ if value < pivot: + return contains_match(left, value) + case pivot, _, right if value > pivot: + return contains_match(right, value) + case (pivot, _, _) | pivot: + return pivot == value + + +assert contains_match(my_tree, 9) +assert not contains_match(my_tree, 14) + +for i in range(0, 14): + print(i, contains_match(my_tree, i)) + + +print("Example 15") +class Node: + def __init__(self, value, left=None, right=None): + self.value = value + self.left = left + self.right = right + + +print("Example 16") +obj_tree = Node( + value=10, + left=Node(value=7, right=9), + right=Node(value=13, left=11), +) + + +print("Example 17") +def contains_class(tree, value): + if not isinstance(tree, Node): + return tree == value + elif value < tree.value: + return contains_class(tree.left, value) + elif value > tree.value: + return contains_class(tree.right, value) + else: + return tree.value == value + + +assert contains_class(obj_tree, 9) +assert not contains_class(obj_tree, 14) + +for i in range(0, 14): + print(i, contains_class(obj_tree, i)) + + +print("Example 18") +def contains_match_class(tree, value): + match tree: + case Node(value=pivot, left=left) if value < pivot: + return contains_match_class(left, value) + case Node(value=pivot, right=right) if value > pivot: + return contains_match_class(right, value) + case Node(value=pivot) | pivot: + return pivot == value + + +assert contains_match_class(obj_tree, 9) +assert not contains_match_class(obj_tree, 14) + +for i in range(0, 14): + print(i, contains_match_class(obj_tree, i)) + + +print("Example 19") +record1 = """{"customer": {"last": "Ross", "first": "Bob"}}""" +record2 = """{"customer": {"entity": "Steve's Painting Co."}}""" + + +print("Example 20") +from dataclasses import dataclass + +@dataclass +class PersonCustomer: + first_name: str + last_name: str + +@dataclass +class BusinessCustomer: + company_name: str + + +print("Example 21") +import json + +def deserialize(data): + record = json.loads(data) + match record: + case {"customer": {"last": last_name, "first": first_name}}: + return PersonCustomer(first_name, last_name) + case {"customer": {"entity": company_name}}: + return BusinessCustomer(company_name) + case _: + raise ValueError("Unknown record type") + + +print("Example 22") +print("Record1:", deserialize(record1)) +print("Record2:", deserialize(record2)) diff --git a/example_code/item_010.py b/example_code/item_010.py new file mode 100755 index 0000000..0974798 --- /dev/null +++ b/example_code/item_010.py @@ -0,0 +1,203 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +a = b"h\x65llo" +print(type(a)) +print(list(a)) +print(a) + + +print("Example 2") +a = "a\u0300 propos" +print(type(a)) +print(list(a)) +print(a) + + +print("Example 3") +def to_str(bytes_or_str): + if isinstance(bytes_or_str, bytes): + value = bytes_or_str.decode("utf-8") + else: + value = bytes_or_str + return value # Instance of str + +print(repr(to_str(b"foo"))) +print(repr(to_str("bar"))) + + +print("Example 4") +def to_bytes(bytes_or_str): + if isinstance(bytes_or_str, str): + value = bytes_or_str.encode("utf-8") + else: + value = bytes_or_str + return value # Instance of bytes + +print(repr(to_bytes(b"foo"))) +print(repr(to_bytes("bar"))) + + +print("Example 5") +print(b"one" + b"two") +print("one" + "two") + + +print("Example 6") +try: + b"one" + "two" +except: + logging.exception('Expected') +else: + assert False + + +print("Example 7") +try: + "one" + b"two" +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +assert b"red" > b"blue" +assert "red" > "blue" + + +print("Example 9") +try: + assert "red" > b"blue" +except: + logging.exception('Expected') +else: + assert False + + +print("Example 10") +try: + assert b"blue" < "red" +except: + logging.exception('Expected') +else: + assert False + + +print("Example 11") +print(b"foo" == "foo") + + +print("Example 12") +blue_bytes = b"blue" +blue_str = "blue" +print(b"red %s" % blue_bytes) +print("red %s" % blue_str) + + +print("Example 13") +try: + print(b"red %s" % blue_str) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 14") +print("red %s" % blue_bytes) +print(f"red {blue_bytes}") + + +print("Example 15") +try: + with open("data.bin", "w") as f: + f.write(b"\xf1\xf2\xf3\xf4\xf5") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 16") +with open("data.bin", "wb") as f: + f.write(b"\xf1\xf2\xf3\xf4\xf5") + + +print("Example 17") +try: + # Silently force UTF-8 here to make sure this test fails on + # all platforms. cp1252 considers these bytes valid on Windows. + real_open = open + + def open(*args, **kwargs): + kwargs["encoding"] = "utf-8" + return real_open(*args, **kwargs) + + with open("data.bin", "r") as f: + data = f.read() +except: + logging.exception('Expected') +else: + assert False + + +print("Example 18") +# Restore the overloaded open above. +open = real_open +with open("data.bin", "rb") as f: + data = f.read() +assert data == b"\xf1\xf2\xf3\xf4\xf5" + + +print("Example 19") +with open("data.bin", "r", encoding="cp1252") as f: + data = f.read() +assert data == "ñòóôõ" diff --git a/example_code/item_011.py b/example_code/item_011.py new file mode 100755 index 0000000..6dae477 --- /dev/null +++ b/example_code/item_011.py @@ -0,0 +1,328 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +a = 0b10111011 +b = 0xC5F +print("Binary is %d, hex is %d" % (a, b)) + + +print("Example 2") +key = "my_var" +value = 1.234 +formatted = "%-10s = %.2f" % (key, value) +print(formatted) + + +print("Example 3") +try: + reordered_tuple = "%-10s = %.2f" % (value, key) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 4") +try: + reordered_string = "%.2f = %-10s" % (key, value) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +pantry = [ + ("avocados", 1.25), + ("bananas", 2.5), + ("cherries", 15), +] +for i, (item, count) in enumerate(pantry): + print("#%d: %-10s = %.2f" % (i, item, count)) + + +print("Example 6") +for i, (item, count) in enumerate(pantry): + print( + "#%d: %-10s = %d" + % ( + i + 1, + item.title(), + round(count), + ) + ) + + +print("Example 7") +template = "%s loves food. See %s cook." +name = "Max" +formatted = template % (name, name) +print(formatted) + + +print("Example 8") +name = "brad" +formatted = template % (name.title(), name) +print(formatted) + + +print("Example 9") +key = "my_var" +value = 1.234 + +old_way = "%-10s = %.2f" % (key, value) + +new_way = "%(key)-10s = %(value).2f" % { + "key": key, # Key first + "value": value, +} + +reordered = "%(key)-10s = %(value).2f" % { + "value": value, + "key": key, # Key second +} + +assert old_way == new_way == reordered + + +print("Example 10") +name = "Max" + +template = "%s loves food. See %s cook." +before = template % (name, name) # Tuple + +template = "%(name)s loves food. See %(name)s cook." +after = template % {"name": name} # Dictionary + +assert before == after + + +print("Example 11") +for i, (item, count) in enumerate(pantry): + before = "#%d: %-10s = %d" % ( + i + 1, + item.title(), + round(count), + ) + + after = "#%(loop)d: %(item)-10s = %(count)d" % { + "loop": i + 1, + "item": item.title(), + "count": round(count), + } + + assert before == after + + +print("Example 12") +soup = "lentil" +formatted = "Today's soup is %(soup)s." % {"soup": soup} +print(formatted) + + +print("Example 13") +menu = { + "soup": "lentil", + "oyster": "kumamoto", + "special": "schnitzel", +} +template = ( + "Today's soup is %(soup)s, " + "buy one get two %(oyster)s oysters, " + "and our special entrée is %(special)s." +) +formatted = template % menu +print(formatted) + + +print("Example 14") +a = 1234.5678 +formatted = format(a, ",.2f") +print(formatted) + +b = "my string" +formatted = format(b, "^20s") +print("*", formatted, "*") + + +print("Example 15") +key = "my_var" +value = 1.234 + +formatted = "{} = {}".format(key, value) +print(formatted) + + +print("Example 16") +formatted = "{:<10} = {:.2f}".format(key, value) +print(formatted) + + +print("Example 17") +print("%.2f%%" % 12.5) +print("{} replaces {{}}".format(1.23)) + + +print("Example 18") +formatted = "{1} = {0}".format(key, value) +print(formatted) + + +print("Example 19") +formatted = "{0} loves food. See {0} cook.".format(name) +print(formatted) + + +print("Example 20") +for i, (item, count) in enumerate(pantry): + old_style = "#%d: %-10s = %d" % ( + i + 1, + item.title(), + round(count), + ) + + new_style = "#{}: {:<10s} = {}".format( + i + 1, + item.title(), + round(count), + ) + + assert old_style == new_style + + +print("Example 21") +formatted = "First letter is {menu[oyster][0]!r}".format(menu=menu) +print(formatted) + + +print("Example 22") +old_template = ( + "Today's soup is %(soup)s, " + "buy one get two %(oyster)s oysters, " + "and our special entrée is %(special)s." +) +old_formatted = old_template % { + "soup": "lentil", + "oyster": "kumamoto", + "special": "schnitzel", +} + +new_template = ( + "Today's soup is {soup}, " + "buy one get two {oyster} oysters, " + "and our special entrée is {special}." +) +new_formatted = new_template.format( + soup="lentil", + oyster="kumamoto", + special="schnitzel", +) + +assert old_formatted == new_formatted + + +print("Example 23") +key = "my_var" +value = 1.234 + +formatted = f"{key} = {value}" +print(formatted) + + +print("Example 24") +formatted = f"{key!r:<10} = {value:.2f}" +print(formatted) + + +print("Example 25") +f_string = f"{key:<10} = {value:.2f}" + +c_tuple = "%-10s = %.2f" % (key, value) + +str_args = "{:<10} = {:.2f}".format(key, value) + +str_kw = "{key:<10} = {value:.2f}".format(key=key, value=value) + +c_dict = "%(key)-10s = %(value).2f" % {"key": key, "value": value} + +assert c_tuple == c_dict == f_string +assert str_args == str_kw == f_string + + +print("Example 26") +for i, (item, count) in enumerate(pantry): + old_style = "#%d: %-10s = %d" % ( + i + 1, + item.title(), + round(count), + ) + + new_style = "#{}: {:<10s} = {}".format( + i + 1, + item.title(), + round(count), + ) + + f_string = f"#{i+1}: {item.title():<10s} = {round(count)}" + + assert old_style == new_style == f_string + + +print("Example 27") +for i, (item, count) in enumerate(pantry): + print(f"#{i+1}: " + f"{item.title():<10s} = " + f"{round(count)}") + + +print("Example 28") +places = 3 +number = 1.23456 +print(f"My number is {number:.{places}f}") diff --git a/example_code/item_012.py b/example_code/item_012.py new file mode 100755 index 0000000..3a43859 --- /dev/null +++ b/example_code/item_012.py @@ -0,0 +1,130 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +print("foo bar") + + +print("Example 2") +my_value = "foo bar" +print(str(my_value)) +print("%s" % my_value) +print(f"{my_value}") +print(format(my_value)) +print(my_value.__format__("s")) +print(my_value.__str__()) + + +print("Example 3") +int_value = 5 +str_value = "5" +print(int_value) +print(str_value) +print(f"Is {int_value} == {str_value}?") + + +print("Example 4") +a = "\x07" +print(repr(a)) + + +print("Example 5") +b = eval(repr(a)) +assert a == b + + +print("Example 6") +print(repr(int_value)) +print(repr(str_value)) + + +print("Example 7") +print("Is %r == %r?" % (int_value, str_value)) +print(f"Is {int_value!r} == {str_value!r}?") + + +print("Example 8") +class OpaqueClass: + def __init__(self, x, y): + self.x = x + self.y = y + +obj = OpaqueClass(1, "foo") +print(obj) + + +print("Example 9") +class BetterClass: + def __init__(self, x, y): + self.x = x + self.y = y + + def __repr__(self): + return f"BetterClass({self.x!r}, {self.y!r})" + + +print("Example 10") +obj = BetterClass(2, "bar") +print(obj) + + +print("Example 11") +print(str(obj)) + + +print("Example 12") +class StringifiableBetterClass(BetterClass): + def __str__(self): + return f"({self.x}, {self.y})" + + +print("Example 13") +obj2 = StringifiableBetterClass(2, "bar") +print("Human readable:", obj2) +print("Printable: ", repr(obj2)) diff --git a/example_code/item_013.py b/example_code/item_013.py new file mode 100755 index 0000000..e78ec9a --- /dev/null +++ b/example_code/item_013.py @@ -0,0 +1,159 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +my_test1 = "hello" "world" +my_test2 = "hello" + "world" +assert my_test1 == my_test2 + + +print("Example 2") +x = 1 +my_test1 = ( + r"first \ part is here with escapes\n, " + f"string interpolation {x} in here, " + 'this has "double quotes" inside' +) +print(my_test1) + + +print("Example 3") +y = 2 +my_test2 = r"fir\st" f"{y}" '"third"' +print(my_test2) + + +print("Example 4") +my_test3 = r"fir\st", f"{y}" '"third"' +print(my_test3) + + +print("Example 5") +my_test4 = [ + "first line\n", + "second line\n", + "third line\n", +] +print(my_test4) + + +print("Example 6") +my_test5 = [ + "first line\n", + "second line\n" # Comma removed + "third line\n", +] +print(my_test5) + + +print("Example 7") +my_test5 = [ + "first line\n", + "second line\n" "third line\n", +] + + +print("Example 8") +my_test6 = [ + "first line\n", + "second line\n" + # Explicit + "third line\n", +] +assert my_test5 == my_test6 + + +print("Example 9") +my_test6 = [ + "first line\n", + "second line\n" + "third line\n", +] + + +print("Example 10") +print("this is my long message " + "that should be printed out") + + +print("Example 11") +import sys + +print("this is my long message " + "that should be printed out", + end="", + file=sys.stderr) + + +print("Example 12") +import sys + +first_value = ... +second_value = ... + +class MyData: + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + +value = MyData(123, + first_value, + f"my format string {x}" + f"another value {y}", + "and here is more text", + second_value, + stream=sys.stderr) + + +print("Example 13") +value2 = MyData(123, + first_value, + f"my format string {x}" + # Explicit + f"another value {y}", + "and here is more text", + second_value, + stream=sys.stderr) diff --git a/example_code/item_014.py b/example_code/item_014.py new file mode 100755 index 0000000..2902a15 --- /dev/null +++ b/example_code/item_014.py @@ -0,0 +1,132 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +a = ["a", "b", "c", "d", "e", "f", "g", "h"] +print("Middle two: ", a[3:5]) +print("All but ends:", a[1:7]) + + +print("Example 2") +assert a[:5] == a[0:5] + + +print("Example 3") +assert a[5:] == a[5:len(a)] + + +print("Example 4") +print(a[:]) +print(a[:5]) +print(a[:-1]) +print(a[4:]) +print(a[-3:]) +print(a[2:5]) +print(a[2:-1]) +print(a[-3:-1]) + + +print("Example 5") +a[:] # ["a", "b", "c", "d", "e", "f", "g", "h"] +a[:5] # ["a", "b", "c", "d", "e"] +a[:-1] # ["a", "b", "c", "d", "e", "f", "g"] +a[4:] # ["e", "f", "g", "h"] +a[-3:] # ["f", "g", "h"] +a[2:5] # ["c", "d", "e"] +a[2:-1] # ["c", "d", "e", "f", "g"] +a[-3:-1] # ["f", "g"] + + +print("Example 6") +first_twenty_items = a[:20] +last_twenty_items = a[-20:] + + +print("Example 7") +try: + a[20] +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +b = a[3:] +print("Before: ", b) +b[1] = 99 +print("After: ", b) +print("No change:", a) + + +print("Example 9") +print("Before ", a) +a[2:7] = [99, 22, 14] +print("After ", a) + + +print("Example 10") +print("Before ", a) +a[2:3] = [47, 11] +print("After ", a) + + +print("Example 11") +b = a[:] +assert b == a and b is not a + + +print("Example 12") +b = a +print("Before a", a) +print("Before b", b) +a[:] = [101, 102, 103] +assert a is b # Still the same list object +print("After a ", a) # Now has different contents +print("After b ", b) # Same list, so same contents as a diff --git a/example_code/item_015.py b/example_code/item_015.py new file mode 100755 index 0000000..0a1bcdb --- /dev/null +++ b/example_code/item_015.py @@ -0,0 +1,106 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +x = ["red", "orange", "yellow", "green", "blue", "purple"] +odds = x[::2] # First, third, fifth +evens = x[1::2] # Second, fourth, sixth +print(odds) +print(evens) + + +print("Example 2") +x = b"mongoose" +y = x[::-1] +print(y) + + +print("Example 3") +x = "寿司" +y = x[::-1] +print(y) + + +print("Example 4") +try: + w = "寿司" + x = w.encode("utf-8") + y = x[::-1] + z = y.decode("utf-8") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +x = ["a", "b", "c", "d", "e", "f", "g", "h"] +x[::2] # ["a", "c", "e", "g"] +x[::-2] # ["h", "f", "d", "b"] +print(x[::2]) +print(x[::-2]) + + +print("Example 6") +x[2::2] # ["c", "e", "g"] +x[-2::-2] # ["g", "e", "c", "a"] +x[-2:2:-2] # ["g", "e"] +x[2:2:-2] # [] +print(x[2::2]) +print(x[-2::-2]) +print(x[-2:2:-2]) +print(x[2:2:-2]) + + +print("Example 7") +y = x[::2] # ["a", "c", "e", "g"] +z = y[1:-1] # ["c", "e"] +print(x) +print(y) +print(z) diff --git a/example_code/item_016.py b/example_code/item_016.py new file mode 100755 index 0000000..bc995ce --- /dev/null +++ b/example_code/item_016.py @@ -0,0 +1,146 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + car_ages = [0, 9, 4, 8, 7, 20, 19, 1, 6, 15] + car_ages_descending = sorted(car_ages, reverse=True) + oldest, second_oldest = car_ages_descending +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +oldest = car_ages_descending[0] +second_oldest = car_ages_descending[1] +others = car_ages_descending[2:] +print(oldest, second_oldest, others) + + +print("Example 3") +oldest, second_oldest, *others = car_ages_descending +print(oldest, second_oldest, others) + + +print("Example 4") +oldest, *others, youngest = car_ages_descending +print(oldest, youngest, others) + +*others, second_youngest, youngest = car_ages_descending +print(youngest, second_youngest, others) + + +print("Example 5") +try: + # This will not compile + source = """*others = car_ages_descending""" + eval(source) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 6") +try: + # This will not compile + source = """first, *middle, *second_middle, last = [1, 2, 3, 4]""" + eval(source) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 7") +car_inventory = { + "Downtown": ("Silver Shadow", "Pinto", "DMC"), + "Airport": ("Skyline", "Viper", "Gremlin", "Nova"), +} +((loc1, (best1, *rest1)), + (loc2, (best2, *rest2))) = car_inventory.items() +print(f"Best at {loc1} is {best1}, {len(rest1)} others") +print(f"Best at {loc2} is {best2}, {len(rest2)} others") + + +print("Example 8") +short_list = [1, 2] +first, second, *rest = short_list +print(first, second, rest) + + +print("Example 9") +it = iter(range(1, 3)) +first, second = it +print(f"{first} and {second}") + + +print("Example 10") +def generate_csv(): + yield ("Date", "Make", "Model", "Year", "Price") + for i in range(100): + yield ("2019-03-25", "Honda", "Fit", "2010", "$3400") + yield ("2019-03-26", "Ford", "F150", "2008", "$2400") + + +print("Example 11") +all_csv_rows = list(generate_csv()) +header = all_csv_rows[0] +rows = all_csv_rows[1:] +print("CSV Header:", header) +print("Row count: ", len(rows)) + + +print("Example 12") +it = generate_csv() +header, *rows = it +print("CSV Header:", header) +print("Row count: ", len(rows)) diff --git a/example_code/item_017.py b/example_code/item_017.py new file mode 100755 index 0000000..700610b --- /dev/null +++ b/example_code/item_017.py @@ -0,0 +1,86 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +from random import randint + +random_bits = 0 +for i in range(32): + if randint(0, 1): + random_bits |= 1 << i + +print(bin(random_bits)) + + +print("Example 2") +flavor_list = ["vanilla", "chocolate", "pecan", "strawberry"] +for flavor in flavor_list: + print(f"{flavor} is delicious") + + +print("Example 3") +for i in range(len(flavor_list)): + flavor = flavor_list[i] + print(f"{i + 1}: {flavor}") + + +print("Example 4") +it = enumerate(flavor_list) +print(next(it)) +print(next(it)) + + +print("Example 5") +for i, flavor in enumerate(flavor_list): + print(f"{i + 1}: {flavor}") + + +print("Example 6") +for i, flavor in enumerate(flavor_list, 1): + print(f"{i}: {flavor}") diff --git a/example_code/item_018.py b/example_code/item_018.py new file mode 100755 index 0000000..dbcec05 --- /dev/null +++ b/example_code/item_018.py @@ -0,0 +1,105 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +names = ["Cecilia", "Lise", "Marie"] +counts = [len(n) for n in names] +print(counts) + + +print("Example 2") +longest_name = None +max_count = 0 + +for i in range(len(names)): + count = counts[i] + if count > max_count: + longest_name = names[i] + max_count = count + +print(longest_name) + + +print("Example 3") +longest_name = None +max_count = 0 + +for i, name in enumerate(names): # Changed + count = counts[i] + if count > max_count: + longest_name = name # Changed + max_count = count +assert longest_name == "Cecilia" + + +print("Example 4") +longest_name = None +max_count = 0 + +for name, count in zip(names, counts): # Changed + if count > max_count: + longest_name = name + max_count = count +assert longest_name == "Cecilia" + + +print("Example 5") +names.append("Rosalind") +for name, count in zip(names, counts): + print(name) + + +print("Example 6") +try: + for name, count in zip(names, counts, strict=True): # Changed + print(name) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_019.py b/example_code/item_019.py new file mode 100755 index 0000000..64b6295 --- /dev/null +++ b/example_code/item_019.py @@ -0,0 +1,114 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +for i in range(3): + print("Loop", i) +else: + print("Else block!") + + +print("Example 2") +for i in range(3): + print("Loop", i) + if i == 1: + break +else: + print("Else block!") + + +print("Example 3") +for x in []: + print("Never runs") +else: + print("For else block!") + + +print("Example 4") +while False: + print("Never runs") +else: + print("While else block!") + + +print("Example 5") +a = 4 +b = 9 + +for i in range(2, min(a, b) + 1): + print("Testing", i) + if a % i == 0 and b % i == 0: + print("Not coprime") + break +else: + print("Coprime") + + +print("Example 6") +def coprime(a, b): + for i in range(2, min(a, b) + 1): + if a % i == 0 and b % i == 0: + return False + return True + +assert coprime(4, 9) +assert not coprime(3, 6) + + +print("Example 7") +def coprime_alternate(a, b): + is_coprime = True + for i in range(2, min(a, b) + 1): + if a % i == 0 and b % i == 0: + is_coprime = False + break + return is_coprime + +assert coprime_alternate(4, 9) +assert not coprime_alternate(3, 6) diff --git a/example_code/item_020.py b/example_code/item_020.py new file mode 100755 index 0000000..39760e3 --- /dev/null +++ b/example_code/item_020.py @@ -0,0 +1,93 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +for i in range(3): + print(f"Inside {i=}") +print(f"After {i=}") + + +print("Example 2") +categories = ["Hydrogen", "Uranium", "Iron", "Other"] +for i, name in enumerate(categories): + if name == "Iron": + break +print(i) + + +print("Example 3") +for i, name in enumerate(categories): + if name == "Lithium": + break +print(i) + + +print("Example 4") +try: + del i + categories = [] + for i, name in enumerate(categories): + if name == "Lithium": + break + print(i) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +try: + my_numbers = [37, 13, 128, 21] + found = [i for i in my_numbers if i % 2 == 0] + print(i) # Always raises +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_021.py b/example_code/item_021.py new file mode 100755 index 0000000..c871012 --- /dev/null +++ b/example_code/item_021.py @@ -0,0 +1,213 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def normalize(numbers): + total = sum(numbers) + result = [] + for value in numbers: + percent = 100 * value / total + result.append(percent) + return result + + +print("Example 2") +visits = [15, 35, 80] +percentages = normalize(visits) +print(percentages) +assert sum(percentages) == 100.0 + + +print("Example 3") +path = "my_numbers.txt" +with open(path, "w") as f: + for i in (15, 35, 80): + f.write(f"{i}\n") + +def read_visits(data_path): + with open(data_path) as f: + for line in f: + yield int(line) + + +print("Example 4") +it = read_visits("my_numbers.txt") +percentages = normalize(it) +print(percentages) + + +print("Example 5") +it = read_visits("my_numbers.txt") +print(list(it)) +print(list(it)) # Already exhausted + + +print("Example 6") +def normalize_copy(numbers): + numbers_copy = list(numbers) # Copy the iterator + total = sum(numbers_copy) + result = [] + for value in numbers_copy: + percent = 100 * value / total + result.append(percent) + return result + + +print("Example 7") +it = read_visits("my_numbers.txt") +percentages = normalize_copy(it) +print(percentages) +assert sum(percentages) == 100.0 + + +print("Example 8") +def normalize_func(get_iter): + total = sum(get_iter()) # New iterator + result = [] + for value in get_iter(): # New iterator + percent = 100 * value / total + result.append(percent) + return result + + +print("Example 9") +path = "my_numbers.txt" +percentages = normalize_func(lambda: read_visits(path)) +print(percentages) +assert sum(percentages) == 100.0 + + +print("Example 10") +class ReadVisits: + def __init__(self, data_path): + self.data_path = data_path + + def __iter__(self): + with open(self.data_path) as f: + for line in f: + yield int(line) + + +print("Example 11") +visits = ReadVisits(path) +percentages = normalize(visits) # Changed +print(percentages) +assert sum(percentages) == 100.0 + + +print("Example 12") +def normalize_defensive(numbers): + if iter(numbers) is numbers: # An iterator -- bad! + raise TypeError("Must supply a container") + total = sum(numbers) + result = [] + for value in numbers: + percent = 100 * value / total + result.append(percent) + return result + + +visits = [15, 35, 80] +normalize_defensive(visits) # No error + +it = iter(visits) +try: + normalize_defensive(it) +except TypeError: + pass +else: + assert False + + +print("Example 13") +from collections.abc import Iterator + +def normalize_defensive(numbers): + if isinstance(numbers, Iterator): # Another way to check + raise TypeError("Must supply a container") + total = sum(numbers) + result = [] + for value in numbers: + percent = 100 * value / total + result.append(percent) + return result + + +visits = [15, 35, 80] +normalize_defensive(visits) # No error + +it = iter(visits) +try: + normalize_defensive(it) +except TypeError: + pass +else: + assert False + + +print("Example 14") +visits_list = [15, 35, 80] +list_percentages = normalize_defensive(visits_list) + +visits_obj = ReadVisits(path) +obj_percentages = normalize_defensive(visits_obj) + +assert list_percentages == obj_percentages +assert sum(percentages) == 100.0 + + +print("Example 15") +try: + visits = [15, 35, 80] + it = iter(visits) + normalize_defensive(it) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_022.py b/example_code/item_022.py new file mode 100755 index 0000000..7147f02 --- /dev/null +++ b/example_code/item_022.py @@ -0,0 +1,217 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + search_key = "red" + my_dict = {"red": 1, "blue": 2, "green": 3} + + for key in my_dict: + if key == "blue": + my_dict["yellow"] = 4 # Causes error +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +try: + my_dict = {"red": 1, "blue": 2, "green": 3} + for key in my_dict: + if key == "blue": + del my_dict["green"] # Causes error +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +my_dict = {"red": 1, "blue": 2, "green": 3} +for key in my_dict: + if key == "blue": + my_dict["green"] = 4 # Okay +print(my_dict) + + +print("Example 4") +try: + my_set = {"red", "blue", "green"} + + for color in my_set: + if color == "blue": + my_set.add("yellow") # Causes error +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +my_set = {"red", "blue", "green"} +for color in my_set: + if color == "blue": + my_set.add("green") # Okay + +print(my_set) + + +print("Example 6") +my_list = [1, 2, 3] + +for number in my_list: + print(number) + if number == 2: + my_list[0] = -1 # Okay + +print(my_list) + + +print("Example 7") +my_list = [1, 2, 3] +bad_count = 0 + +for number in my_list: + print(number) + if number == 2: + my_list.insert(0, 4) # Causes error + # Break out of the infinite loop + bad_count += 1 + if bad_count > 5: + print("...") + break + + +print("Example 8") +my_list = [1, 2, 3] + +for number in my_list: + print(number) + if number == 2: + my_list.append(4) # Okay this time + +print(my_list) + + +print("Example 9") +my_dict = {"red": 1, "blue": 2, "green": 3} + +keys_copy = list(my_dict.keys()) # Copy +for key in keys_copy: # Iterate over copy + if key == "blue": + my_dict["green"] = 4 # Modify original dict + +print(my_dict) + + +print("Example 10") +my_list = [1, 2, 3] + +list_copy = list(my_list) # Copy +for number in list_copy: # Iterate over copy + print(number) + if number == 2: + my_list.insert(0, 4) # Inserts in original list + +print(my_list) + + +print("Example 11") +my_set = {"red", "blue", "green"} + +set_copy = set(my_set) # Copy +for color in set_copy: # Iterate over copy + if color == "blue": + my_set.add("yellow") # Add to original set + +print(my_set) + + +print("Example 12") +my_dict = {"red": 1, "blue": 2, "green": 3} +modifications = {} + +for key in my_dict: + if key == "blue": + modifications["green"] = 4 # Add to staging + +my_dict.update(modifications) # Merge modifications +print(my_dict) + + +print("Example 13") +my_dict = {"red": 1, "blue": 2, "green": 3} +modifications = {} + +for key in my_dict: + if key == "blue": + modifications["green"] = 4 + value = my_dict[key] + if value == 4: # This condition is never true + modifications["yellow"] = 5 + +my_dict.update(modifications) # Merge modifications +print(my_dict) + + +print("Example 14") +my_dict = {"red": 1, "blue": 2, "green": 3} +modifications = {} + +for key in my_dict: + if key == "blue": + modifications["green"] = 4 + value = my_dict[key] + other_value = modifications.get(key) # Check cache + if value == 4 or other_value == 4: + modifications["yellow"] = 5 + +my_dict.update(modifications) # Merge modifications +print(my_dict) diff --git a/example_code/item_023.py b/example_code/item_023.py new file mode 100755 index 0000000..39ef183 --- /dev/null +++ b/example_code/item_023.py @@ -0,0 +1,140 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import random + +def flip_coin(): + if random.randint(0, 1) == 0: + return "Heads" + else: + return "Tails" + +def flip_is_heads(): + return flip_coin() == "Heads" + + +print("Example 2") +flips = [flip_is_heads() for _ in range(20)] +all_heads = False not in flips +assert not all_heads # Very unlikely to be True + + +print("Example 3") +all_heads = True +for _ in range(100): + if not flip_is_heads(): + all_heads = False + break +assert not all_heads # Very unlikely to be True + + +print("Example 4") +print("All truthy:") +print(all([1, 2, 3])) +print(1 and 2 and 3) + +print("One falsey:") +print(all([1, 0, 3])) +print(1 and 0 and 3) + + +print("Example 5") +all_heads = all(flip_is_heads() for _ in range(20)) +assert not all_heads + + +print("Example 6") +all_heads = all([flip_is_heads() for _ in range(20)]) # Wrong +assert not all_heads + + +print("Example 7") +def repeated_is_heads(count): + for _ in range(count): + yield flip_is_heads() # Generator + +all_heads = all(repeated_is_heads(20)) +assert not all_heads + + +print("Example 8") +def flip_is_tails(): + return flip_coin() == "Tails" + + +print("Example 9") +print("All falsey:") +print(any([0, False, None])) +print(0 or False or None) + +print("One truthy:") +print(any([None, 3, 0])) +print(None or 3 or 0) + + +print("Example 10") +all_heads = not any(flip_is_tails() for _ in range(20)) +assert not all_heads + + +print("Example 11") +def repeated_is_tails(count): + for _ in range(count): + yield flip_is_tails() + +all_heads = not any(repeated_is_tails(20)) +assert not all_heads + + +print("Example 12") +for a in (True, False): + for b in (True, False): + assert any([a, b]) == (not all([not a, not b])) + assert all([a, b]) == (not any([not a, not b])) diff --git a/example_code/item_024.py b/example_code/item_024.py new file mode 100755 index 0000000..bafaab7 --- /dev/null +++ b/example_code/item_024.py @@ -0,0 +1,187 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import itertools + + +print("Example 2") +it = itertools.chain([1, 2, 3], [4, 5, 6]) +print(list(it)) + + +print("Example 3") +it1 = [i * 3 for i in ("a", "b", "c")] +it2 = [j * 2 for j in ("x", "y", "z")] +nested_it = [it1, it2] +output_it = itertools.chain.from_iterable(nested_it) +print(list(output_it)) + + +print("Example 4") +it = itertools.repeat("hello", 3) +print(list(it)) + + +print("Example 5") +it = itertools.cycle([1, 2]) +result = [next(it) for _ in range(10)] +print(result) + + +print("Example 6") +it1, it2, it3 = itertools.tee(["first", "second"], 3) +print(list(it1)) +print(list(it2)) +print(list(it3)) + + +print("Example 7") +keys = ["one", "two", "three"] +values = [1, 2] + +normal = list(zip(keys, values)) +print("zip: ", normal) + +it = itertools.zip_longest(keys, values, fillvalue="nope") +longest = list(it) +print("zip_longest:", longest) + + +print("Example 8") +values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +first_five = itertools.islice(values, 5) +print("First five: ", list(first_five)) + +middle_odds = itertools.islice(values, 2, 8, 2) +print("Middle odds:", list(middle_odds)) + + +print("Example 9") +values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +less_than_seven = lambda x: x < 7 +it = itertools.takewhile(less_than_seven, values) +print(list(it)) + + +print("Example 10") +values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +less_than_seven = lambda x: x < 7 +it = itertools.dropwhile(less_than_seven, values) +print(list(it)) + + +print("Example 11") +values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +evens = lambda x: x % 2 == 0 + +filter_result = filter(evens, values) +print("Filter: ", list(filter_result)) + +filter_false_result = itertools.filterfalse(evens, values) +print("Filter false:", list(filter_false_result)) + + +print("Example 12") +it = itertools.batched([1, 2, 3, 4, 5, 6, 7, 8, 9], 3) +print(list(it)) + + +print("Example 13") +it = itertools.batched([1, 2, 3], 2) +print(list(it)) + + +print("Example 14") +route = ["Los Angeles", "Bakersfield", "Modesto", "Sacramento"] +it = itertools.pairwise(route) +print(list(it)) + + +print("Example 15") +values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +sum_reduce = itertools.accumulate(values) +print("Sum: ", list(sum_reduce)) + +def sum_modulo_20(first, second): + output = first + second + return output % 20 + +modulo_reduce = itertools.accumulate(values, sum_modulo_20) +print("Modulo:", list(modulo_reduce)) + + +print("Example 16") +single = itertools.product([1, 2], repeat=2) +print("Single: ", list(single)) + +multiple = itertools.product([1, 2], ["a", "b"]) +print("Multiple:", list(multiple)) + + +print("Example 17") +it = itertools.permutations([1, 2, 3, 4], 2) +original_print = print +print = pprint +print(list(it)) +print = original_print + + +print("Example 18") +it = itertools.combinations([1, 2, 3, 4], 2) +print(list(it)) + + +print("Example 19") +it = itertools.combinations_with_replacement([1, 2, 3, 4], 2) +original_print = print +print = pprint +print(list(it)) +print = original_print diff --git a/example_code/item_025.py b/example_code/item_025.py new file mode 100755 index 0000000..9462683 --- /dev/null +++ b/example_code/item_025.py @@ -0,0 +1,189 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 2") +baby_names = { + "cat": "kitten", + "dog": "puppy", +} +print(baby_names) + + +print("Example 4") +print(list(baby_names.keys())) +print(list(baby_names.values())) +print(list(baby_names.items())) +print(baby_names.popitem()) # Last item inserted + + +print("Example 6") +def my_func(**kwargs): + for key, value in kwargs.items(): + print(f"{key} = {value}") + +my_func(goose="gosling", kangaroo="joey") + + +print("Example 8") +class MyClass: + def __init__(self): + self.alligator = "hatchling" + self.elephant = "calf" + +a = MyClass() +for key, value in a.__dict__.items(): + print(f"{key} = {value}") + + +print("Example 9") +votes = { + "otter": 1281, + "polar bear": 587, + "fox": 863, +} + + +print("Example 10") +def populate_ranks(votes, ranks): + names = list(votes.keys()) + names.sort(key=votes.get, reverse=True) + for i, name in enumerate(names, 1): + ranks[name] = i + + +print("Example 11") +def get_winner(ranks): + return next(iter(ranks)) + + +print("Example 12") +ranks = {} +populate_ranks(votes, ranks) +print(ranks) +winner = get_winner(ranks) +print(winner) + + +print("Example 13") +from collections.abc import MutableMapping + +class SortedDict(MutableMapping): + def __init__(self): + self.data = {} + + def __getitem__(self, key): + return self.data[key] + + def __setitem__(self, key, value): + self.data[key] = value + + def __delitem__(self, key): + del self.data[key] + + def __iter__(self): + keys = list(self.data.keys()) + keys.sort() + for key in keys: + yield key + + def __len__(self): + return len(self.data) + + +my_dict = SortedDict() +my_dict["otter"] = 1 +my_dict["cheeta"] = 2 +my_dict["anteater"] = 3 +my_dict["deer"] = 4 + +assert my_dict["otter"] == 1 + +assert "cheeta" in my_dict +del my_dict["cheeta"] +assert "cheeta" not in my_dict + +expected = [("anteater", 3), ("deer", 4), ("otter", 1)] +assert list(my_dict.items()) == expected + +assert not isinstance(my_dict, dict) + + +print("Example 14") +sorted_ranks = SortedDict() +populate_ranks(votes, sorted_ranks) +print(sorted_ranks.data) +winner = get_winner(sorted_ranks) +print(winner) + + +print("Example 15") +def get_winner(ranks): + for name, rank in ranks.items(): + if rank == 1: + return name + +winner = get_winner(sorted_ranks) +print(winner) + + +print("Example 16") +try: + def get_winner(ranks): + if not isinstance(ranks, dict): + raise TypeError("must provide a dict instance") + return next(iter(ranks)) + + + assert get_winner(ranks) == "otter" + + get_winner(sorted_ranks) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_025_example_01.py b/example_code/item_025_example_01.py new file mode 100755 index 0000000..8c83fcb --- /dev/null +++ b/example_code/item_025_example_01.py @@ -0,0 +1,25 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3.5 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 1") +# Python 3.5 +baby_names = { + "cat": "kitten", + "dog": "puppy", +} +print(baby_names) diff --git a/example_code/item_025_example_03.py b/example_code/item_025_example_03.py new file mode 100755 index 0000000..7b89df6 --- /dev/null +++ b/example_code/item_025_example_03.py @@ -0,0 +1,28 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3.5 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 3") +# Python 3.5 +baby_names = { + "cat": "kitten", + "dog": "puppy", +} +print(list(baby_names.keys())) +print(list(baby_names.values())) +print(list(baby_names.items())) +print(baby_names.popitem()) # Randomly chooses an item diff --git a/example_code/item_025_example_05.py b/example_code/item_025_example_05.py new file mode 100755 index 0000000..ed7a41c --- /dev/null +++ b/example_code/item_025_example_05.py @@ -0,0 +1,25 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3.5 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 5") +# Python 3.5 +def my_func(**kwargs): + for key, value in kwargs.items(): + print("%s = %s" % (key, value)) + +my_func(goose="gosling", kangaroo="joey") diff --git a/example_code/item_025_example_07.py b/example_code/item_025_example_07.py new file mode 100755 index 0000000..2b8467d --- /dev/null +++ b/example_code/item_025_example_07.py @@ -0,0 +1,28 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3.5 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 7") +# Python 3.5 +class MyClass: + def __init__(self): + self.alligator = "hatchling" + self.elephant = "calf" + +a = MyClass() +for key, value in a.__dict__.items(): + print("%s = %s" % (key, value)) diff --git a/example_code/item_025_example_17.py b/example_code/item_025_example_17.py new file mode 100755 index 0000000..cb6f973 --- /dev/null +++ b/example_code/item_025_example_17.py @@ -0,0 +1,68 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 17") +# Check types in this file with: python3 -m mypy + +from typing import Dict, MutableMapping + +def populate_ranks(votes: Dict[str, int], ranks: Dict[str, int]) -> None: + names = list(votes.keys()) + names.sort(key=votes.__getitem__, reverse=True) + for i, name in enumerate(names, 1): + ranks[name] = i + +def get_winner(ranks: Dict[str, int]) -> str: + return next(iter(ranks)) + +from typing import Iterator, MutableMapping + +class SortedDict(MutableMapping[str, int]): + def __init__(self) -> None: + self.data: Dict[str, int] = {} + + def __getitem__(self, key: str) -> int: + return self.data[key] + + def __setitem__(self, key: str, value: int) -> None: + self.data[key] = value + + def __delitem__(self, key: str) -> None: + del self.data[key] + + def __iter__(self) -> Iterator[str]: + keys = list(self.data.keys()) + keys.sort() + for key in keys: + yield key + + def __len__(self) -> int: + return len(self.data) + + +votes = { + "otter": 1281, + "polar bear": 587, + "fox": 863, +} + +sorted_ranks = SortedDict() +populate_ranks(votes, sorted_ranks) +print(sorted_ranks.data) +winner = get_winner(sorted_ranks) +print(winner) diff --git a/example_code/item_026.py b/example_code/item_026.py new file mode 100755 index 0000000..c6c58d8 --- /dev/null +++ b/example_code/item_026.py @@ -0,0 +1,198 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +counters = { + "pumpernickel": 2, + "sourdough": 1, +} + + +print("Example 2") +key = "wheat" + +if key in counters: + count = counters[key] +else: + count = 0 + +counters[key] = count + 1 +print(counters) + + +print("Example 3") +key = "brioche" + +try: + count = counters[key] +except KeyError: + count = 0 + +counters[key] = count + 1 + +print(counters) + + +print("Example 4") +key = "multigrain" + +count = counters.get(key, 0) +counters[key] = count + 1 + +print(counters) + + +print("Example 5") +key = "baguette" + +if key not in counters: + counters[key] = 0 +counters[key] += 1 + +key = "ciabatta" + +if key in counters: + counters[key] += 1 +else: + counters[key] = 1 + +key = "ciabatta" + +try: + counters[key] += 1 +except KeyError: + counters[key] = 1 + +print(counters) + + +print("Example 6") +votes = { + "baguette": ["Bob", "Alice"], + "ciabatta": ["Coco", "Deb"], +} + +key = "brioche" +who = "Elmer" + +if key in votes: + names = votes[key] +else: + votes[key] = names = [] + +names.append(who) +print(votes) + + +print("Example 7") +key = "rye" +who = "Felix" + +try: + names = votes[key] +except KeyError: + votes[key] = names = [] + +names.append(who) + +print(votes) + + +print("Example 8") +key = "wheat" +who = "Gertrude" + +names = votes.get(key) +if names is None: + votes[key] = names = [] + +names.append(who) + +print(votes) + + +print("Example 9") +key = "brioche" +who = "Hugh" + +if (names := votes.get(key)) is None: + votes[key] = names = [] + +names.append(who) + +print(votes) + + +print("Example 10") +key = "cornbread" +who = "Kirk" + +names = votes.setdefault(key, []) +names.append(who) + +print(votes) + + +print("Example 11") +data = {} +key = "foo" +value = [] +data.setdefault(key, value) +print("Before:", data) +value.append("hello") +print("After: ", data) + + +print("Example 12") +key = "dutch crunch" + +count = counters.setdefault(key, 0) +counters[key] = count + 1 + +print(counters) diff --git a/example_code/item_027.py b/example_code/item_027.py new file mode 100755 index 0000000..9c5732b --- /dev/null +++ b/example_code/item_027.py @@ -0,0 +1,102 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +visits = { + "Mexico": {"Tulum", "Puerto Vallarta"}, + "Japan": {"Hakone"}, +} + + +print("Example 2") +# Short +visits.setdefault("France", set()).add("Arles") + +# Long +if (japan := visits.get("Japan")) is None: + visits["Japan"] = japan = set() + +japan.add("Kyoto") +original_print = print +print = pprint +print(visits) +print = original_print + + +print("Example 3") +class Visits: + def __init__(self): + self.data = {} + + def add(self, country, city): + city_set = self.data.setdefault(country, set()) + city_set.add(city) + + +print("Example 4") +visits = Visits() +visits.add("Russia", "Yekaterinburg") +visits.add("Tanzania", "Zanzibar") +print(visits.data) + + +print("Example 5") +from collections import defaultdict + +class Visits: + def __init__(self): + self.data = defaultdict(set) + + def add(self, country, city): + self.data[country].add(city) + +visits = Visits() +visits.add("England", "Bath") +visits.add("England", "London") +print(visits.data) diff --git a/example_code/item_028.py b/example_code/item_028.py new file mode 100755 index 0000000..11903ed --- /dev/null +++ b/example_code/item_028.py @@ -0,0 +1,192 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +pictures = {} +path = "profile_1234.png" + +with open(path, "wb") as f: + f.write(b"image data here 1234") + +if (handle := pictures.get(path)) is None: + try: + handle = open(path, "a+b") + except OSError: + print(f"Failed to open path {path}") + raise + else: + pictures[path] = handle + +handle.seek(0) +image_data = handle.read() + +print(pictures) +print(image_data) + + +print("Example 2") +# Examples using in and KeyError +pictures = {} +path = "profile_9991.png" + +with open(path, "wb") as f: + f.write(b"image data here 9991") + +if path in pictures: + handle = pictures[path] +else: + try: + handle = open(path, "a+b") + except OSError: + print(f"Failed to open path {path}") + raise + else: + pictures[path] = handle + +handle.seek(0) +image_data = handle.read() + +print(pictures) +print(image_data) + +pictures = {} +path = "profile_9922.png" + +with open(path, "wb") as f: + f.write(b"image data here 9991") + +try: + handle = pictures[path] +except KeyError: + try: + handle = open(path, "a+b") + except OSError: + print(f"Failed to open path {path}") + raise + else: + pictures[path] = handle + +handle.seek(0) +image_data = handle.read() + +print(pictures) +print(image_data) + + +print("Example 3") +pictures = {} +path = "profile_9239.png" + +with open(path, "wb") as f: + f.write(b"image data here 9239") + +try: + handle = pictures.setdefault(path, open(path, "a+b")) +except OSError: + print(f"Failed to open path {path}") + raise +else: + handle.seek(0) + image_data = handle.read() + +print(pictures) +print(image_data) + + +print("Example 4") +try: + path = "profile_4555.csv" + + with open(path, "wb") as f: + f.write(b"image data here 9239") + + from collections import defaultdict + + def open_picture(profile_path): + try: + return open(profile_path, "a+b") + except OSError: + print(f"Failed to open path {profile_path}") + raise + + pictures = defaultdict(open_picture) + handle = pictures[path] + handle.seek(0) + image_data = handle.read() +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +path = "account_9090.csv" + +with open(path, "wb") as f: + f.write(b"image data here 9090") + +def open_picture(profile_path): + try: + return open(profile_path, "a+b") + except OSError: + print(f"Failed to open path {profile_path}") + raise + +class Pictures(dict): + def __missing__(self, key): + value = open_picture(key) + self[key] = value + return value + +pictures = Pictures() +handle = pictures[path] +handle.seek(0) +image_data = handle.read() +print(pictures) +print(image_data) diff --git a/example_code/item_22.py b/example_code/item_029.py similarity index 53% rename from example_code/item_22.py rename to example_code/item_029.py index 3c31804..ca593cd 100755 --- a/example_code/item_22.py +++ b/example_code/item_029.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,14 +14,41 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment +### 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() -# Example 1 -class SimpleGradebook(object): +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class SimpleGradebook: def __init__(self): self._grades = {} @@ -36,28 +63,29 @@ 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) -print(book.average_grade('Isaac Newton')) +book.add_student("Isaac Newton") +book.report_grade("Isaac Newton", 90) +book.report_grade("Isaac Newton", 95) +book.report_grade("Isaac Newton", 85) + +print(book.average_grade("Isaac Newton")) + +print("Example 3") +from collections import defaultdict -# Example 3 -class BySubjectGradebook(object): +class BySubjectGradebook: def __init__(self): - self._grades = {} + self._grades = {} # Outer dict def add_student(self, name): - self._grades[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.setdefault(subject, []) + grade_list = by_subject[subject] grade_list.append(grade) def average_grade(self, name): @@ -69,56 +97,57 @@ 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 -class WeightedGradebook(object): +print("Example 5") +class WeightedGradebook: def __init__(self): self._grades = {} def add_student(self, name): - self._grades[name] = {} + self._grades[name] = defaultdict(list) def report_grade(self, name, subject, score, weight): by_subject = self._grades[name] - grade_list = by_subject.setdefault(subject, []) - grade_list.append((score, weight)) + grade_list = by_subject[subject] + 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 + score_sum += subject_avg / total_weight score_count += 1 + return score_sum / score_count -# Example 8 +print("Example 6") book = WeightedGradebook() -book.add_student('Albert Einstein') -book.report_grade('Albert Einstein', 'Math', 80, 0.10) -book.report_grade('Albert Einstein', 'Math', 80, 0.10) -book.report_grade('Albert Einstein', 'Math', 70, 0.80) -book.report_grade('Albert Einstein', 'Gym', 100, 0.40) -book.report_grade('Albert Einstein', 'Gym', 85, 0.60) -print(book.average_grade('Albert Einstein')) +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)) @@ -128,23 +157,27 @@ 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 -import collections -Grade = collections.namedtuple('Grade', ('score', 'weight')) +print("Example 9") +from dataclasses import dataclass + +@dataclass(frozen=True) +class Grade: + score: int + weight: float -# Example 12 -class Subject(object): +print("Example 10") +class Subject: def __init__(self): self._grades = [] @@ -159,14 +192,12 @@ def average_grade(self): return total / total_weight -# Example 13 -class Student(object): +print("Example 11") +class Student: def __init__(self): - self._subjects = {} + self._subjects = defaultdict(Subject) - def subject(self, name): - if name not in self._subjects: - self._subjects[name] = Subject() + def get_subject(self, name): return self._subjects[name] def average_grade(self): @@ -177,25 +208,23 @@ def average_grade(self): return total / count -# Example 14 -class Gradebook(object): +print("Example 12") +class Gradebook: def __init__(self): - self._students = {} + self._students = defaultdict(Student) - def student(self, name): - if name not in self._students: - self._students[name] = Student() + def get_student(self, name): return self._students[name] -# Example 15 +print("Example 13") book = Gradebook() -albert = book.student('Albert Einstein') -math = albert.subject('Math') -math.report_grade(80, 0.10) -math.report_grade(80, 0.10) +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.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_03.py b/example_code/item_03.py deleted file mode 100755 index f4e4590..0000000 --- a/example_code/item_03.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def to_str(bytes_or_str): - if isinstance(bytes_or_str, bytes): - value = bytes_or_str.decode('utf-8') - else: - value = bytes_or_str - return value # Instance of str - -print(repr(to_str(b'foo'))) -print(repr(to_str('foo'))) - - -# Example 2 -def to_bytes(bytes_or_str): - if isinstance(bytes_or_str, str): - value = bytes_or_str.encode('utf-8') - else: - value = bytes_or_str - return value # Instance of bytes - -print(repr(to_bytes(b'foo'))) -print(repr(to_bytes('foo'))) - - -# Example 5 -try: - import os - with open('random.bin', 'w') as f: - f.write(os.urandom(10)) -except: - logging.exception('Expected') -else: - assert False - - -# Example 6 -with open('random.bin', 'wb') as f: - f.write(os.urandom(10)) diff --git a/example_code/item_030.py b/example_code/item_030.py new file mode 100755 index 0000000..3831909 --- /dev/null +++ b/example_code/item_030.py @@ -0,0 +1,99 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def my_func(items): + items.append(4) + +x = [1, 2, 3] +my_func(x) +print(x) # 4 is now in the list + + +print("Example 2") +a = [7, 6, 5] +b = a # Creates an alias +my_func(b) +print(a) # 4 is now in the list + + +print("Example 3") +def capitalize_items(items): + for i in range(len(items)): + items[i] = items[i].capitalize() + +my_items = ["hello", "world"] +items_copy = my_items[:] # Creates a copy +capitalize_items(items_copy) +print(items_copy) + + +print("Example 4") +def concat_pairs(items): + for key in items: + items[key] = f"{key}={items[key]}" + +my_pairs = {"foo": 1, "bar": 2} +pairs_copy = my_pairs.copy() # Creates a copy +concat_pairs(pairs_copy) +print(pairs_copy) + + +print("Example 5") +class MyClass: + def __init__(self, value): + self.value = value + +x = MyClass(10) + +def my_func(obj): + obj.value = 20 # Modifies the object + +my_func(x) +print(x.value) diff --git a/example_code/item_031.py b/example_code/item_031.py new file mode 100755 index 0000000..1535b70 --- /dev/null +++ b/example_code/item_031.py @@ -0,0 +1,181 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def get_stats(numbers): + minimum = min(numbers) + maximum = max(numbers) + return minimum, maximum + +lengths = [63, 73, 72, 60, 67, 66, 71, 61, 72, 70] + +minimum, maximum = get_stats(lengths) # Two return values + +print(f"Min: {minimum}, Max: {maximum}") + + +print("Example 2") +first, second = 1, 2 +assert first == 1 +assert second == 2 + +def my_function(): + return 1, 2 + +first, second = my_function() +assert first == 1 +assert second == 2 + + +print("Example 3") +def get_avg_ratio(numbers): + average = sum(numbers) / len(numbers) + scaled = [x / average for x in numbers] + scaled.sort(reverse=True) + return scaled + +longest, *middle, shortest = get_avg_ratio(lengths) + +print(f"Longest: {longest:>4.0%}") +print(f"Shortest: {shortest:>4.0%}") + + +print("Example 4") +def get_median(numbers): + count = len(numbers) + sorted_numbers = sorted(numbers) + middle = count // 2 + if count % 2 == 0: + lower = sorted_numbers[middle - 1] + upper = sorted_numbers[middle] + median = (lower + upper) / 2 + else: + median = sorted_numbers[middle] + return median + +def get_stats_more(numbers): + minimum = min(numbers) + maximum = max(numbers) + count = len(numbers) + average = sum(numbers) / count + median = get_median(numbers) + return minimum, maximum, average, median, count + +minimum, maximum, average, median, count = get_stats_more(lengths) + +print(f"Min: {minimum}, Max: {maximum}") +print(f"Average: {average}, Median: {median}, Count {count}") + +assert minimum == 60 +assert maximum == 73 +assert average == 67.5 +assert median == 68.5 +assert count == 10 + +# Verify odd count median +_, _, _, median, count = get_stats_more([1, 2, 3]) +assert median == 2 +assert count == 3 + + +print("Example 5") +# Correct: +minimum, maximum, average, median, count = get_stats_more(lengths) + +# Oops! Median and average swapped: +minimum, maximum, median, average, count = get_stats_more(lengths) + + +print("Example 6") +minimum, maximum, average, median, count = get_stats_more( + lengths) + +minimum, maximum, average, median, count = \ + get_stats_more(lengths) + +(minimum, maximum, average, + median, count) = get_stats_more(lengths) + +(minimum, maximum, average, median, count + ) = get_stats_more(lengths) + + +print("Example 7") +from dataclasses import dataclass + +@dataclass +class Stats: + minimum: float + maximum: float + average: float + median: float + count: int + +def get_stats_obj(numbers): + return Stats( + minimum=min(numbers), + maximum=max(numbers), + count=len(numbers), + average=sum(numbers) / count, + median=get_median(numbers), + ) + +result = get_stats_obj(lengths) +print(result) + +assert result.minimum == 60 +assert result.maximum == 73 +assert result.average == 67.5 +assert result.median == 68.5 +assert result.count == 10 + +# Verify odd count median +result2 = get_stats_obj([1, 2, 3]) +assert result2.median == 2 +assert result2.count == 3 diff --git a/example_code/item_032.py b/example_code/item_032.py new file mode 100755 index 0000000..859860e --- /dev/null +++ b/example_code/item_032.py @@ -0,0 +1,125 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def careful_divide(a, b): + try: + return a / b + except ZeroDivisionError: + return None + + +assert careful_divide(4, 2) == 2 +assert careful_divide(0, 1) == 0 +assert careful_divide(3, 6) == 0.5 +assert careful_divide(1, 0) == None + + +print("Example 2") +x, y = 1, 0 +result = careful_divide(x, y) +if result is None: + print("Invalid inputs") +else: + print(f"Result is {result:.1f}") + + +print("Example 3") +x, y = 0, 5 +result = careful_divide(x, y) +if not result: # Changed + print("Invalid inputs") # This runs! But shouldn't +else: + assert False + + +print("Example 4") +def careful_divide(a, b): + try: + return True, a / b + except ZeroDivisionError: + return False, None + + +assert careful_divide(4, 2) == (True, 2) +assert careful_divide(0, 1) == (True, 0) +assert careful_divide(3, 6) == (True, 0.5) +assert careful_divide(1, 0) == (False, None) + + +print("Example 5") +x, y = 5, 0 +success, result = careful_divide(x, y) +if not success: + print("Invalid inputs") + + +print("Example 6") +x, y = 5, 0 +_, result = careful_divide(x, y) +if not result: + print("Invalid inputs") + + +print("Example 7") +def careful_divide(a, b): + try: + return a / b + except ZeroDivisionError: + raise ValueError("Invalid inputs") # Changed + + +print("Example 8") +x, y = 5, 2 +try: + result = careful_divide(x, y) +except ValueError: + print("Invalid inputs") +else: + print(f"Result is {result:.1f}") diff --git a/example_code/item_032_example_09.py b/example_code/item_032_example_09.py new file mode 100755 index 0000000..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_15.py b/example_code/item_033.py similarity index 55% rename from example_code/item_15.py rename to example_code/item_033.py index 5536610..0124f84 100755 --- a/example_code/item_15.py +++ b/example_code/item_033.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,79 +14,124 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment +### 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 + -# 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: + logging.exception('Expected') +else: + assert False + + +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 6 +print("Example 7") def sort_priority3(numbers, group): found = False + def helper(x): - nonlocal found + nonlocal found # Added if x in group: found = True return (0, x) return (1, x) + numbers.sort(key=helper) return found -# Example 7 +print("Example 8") +numbers = [8, 3, 1, 2, 5, 4, 7, 6] found = sort_priority3(numbers, group) -print('Found:', found) +print("Found:", found) print(numbers) -# Example 8 -class Sorter(object): +print("Example 9") +class Sorter: def __init__(self, group): self.group = group self.found = False @@ -97,8 +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 -print('Found:', found) +print("Found:", sorter.found) print(numbers) diff --git a/example_code/item_034.py b/example_code/item_034.py new file mode 100755 index 0000000..1c54553 --- /dev/null +++ b/example_code/item_034.py @@ -0,0 +1,101 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def log(message, values): + if not values: + print(message) + else: + values_str = ", ".join(str(x) for x in values) + print(f"{message}: {values_str}") + +log("My numbers are", [1, 2]) +log("Hi there", []) + + +print("Example 2") +def log(message, *values): # Changed + if not values: + print(message) + else: + values_str = ", ".join(str(x) for x in values) + print(f"{message}: {values_str}") + +log("My numbers are", 1, 2) +log("Hi there") # Changed + + +print("Example 3") +favorites = [7, 33, 99] +log("Favorite colors", *favorites) + + +print("Example 4") +def my_generator(): + for i in range(10): + yield i + +def my_func(*args): + print(args) + +it = my_generator() +my_func(*it) + + +print("Example 5") +def log_seq(sequence, message, *values): + if not values: + print(f"{sequence} - {message}") + else: + values_str = ", ".join(str(x) for x in values) + print(f"{sequence} - {message}: {values_str}") + +log_seq(1, "Favorites", 7, 33) # New with *args OK +log_seq(1, "Hi there") # New message only OK +log_seq("Favorite numbers", 7, 33) # Old usage breaks diff --git a/example_code/item_035.py b/example_code/item_035.py new file mode 100755 index 0000000..0b5ab67 --- /dev/null +++ b/example_code/item_035.py @@ -0,0 +1,169 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def remainder(number, divisor): + return number % divisor + +assert remainder(20, 7) == 6 + + +print("Example 2") +remainder(20, 7) +remainder(20, divisor=7) +remainder(number=20, divisor=7) +remainder(divisor=7, number=20) + + +print("Example 3") +try: + # This will not compile + source = """remainder(number=20, 7)""" + eval(source) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 4") +try: + remainder(20, number=7) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +my_kwargs = { + "number": 20, + "divisor": 7, +} +assert remainder(**my_kwargs) == 6 + + +print("Example 6") +my_kwargs = { + "divisor": 7, +} +assert remainder(number=20, **my_kwargs) == 6 + + +print("Example 7") +my_kwargs = { + "number": 20, +} +other_kwargs = { + "divisor": 7, +} +assert remainder(**my_kwargs, **other_kwargs) == 6 + + +print("Example 8") +def print_parameters(**kwargs): + for key, value in kwargs.items(): + print(f"{key} = {value}") + +print_parameters(alpha=1.5, beta=9, gamma=4) + + +print("Example 9") +def flow_rate(weight_diff, time_diff): + return weight_diff / time_diff + +weight_a = 2.5 +weight_b = 3 +time_a = 1 +time_b = 4 +weight_diff = weight_b - weight_a +time_diff = time_b - time_a +flow = flow_rate(weight_diff, time_diff) +print(f"{flow:.3} kg per second") + + +print("Example 10") +def flow_rate(weight_diff, time_diff, period): + return (weight_diff / time_diff) * period + + +print("Example 11") +flow_per_second = flow_rate(weight_diff, time_diff, 1) + + +print("Example 12") +def flow_rate(weight_diff, time_diff, period=1): # Changed + return (weight_diff / time_diff) * period + + +print("Example 13") +flow_per_second = flow_rate(weight_diff, time_diff) +flow_per_hour = flow_rate(weight_diff, time_diff, period=3600) +print(flow_per_second) +print(flow_per_hour) + + +print("Example 14") +def flow_rate(weight_diff, time_diff, period=1, units_per_kg=1): + return ((weight_diff * units_per_kg) / time_diff) * period + + +print("Example 15") +pounds_per_hour = flow_rate( + weight_diff, + time_diff, + period=3600, + units_per_kg=2.2, +) +print(pounds_per_hour) + + +print("Example 16") +pounds_per_hour = flow_rate(weight_diff, time_diff, 3600, 2.2) +print(pounds_per_hour) diff --git a/example_code/item_20.py b/example_code/item_036.py similarity index 51% rename from example_code/item_20.py rename to example_code/item_036.py index d176320..1a0c005 100755 --- a/example_code/item_20.py +++ b/example_code/item_036.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,25 +14,52 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment +### 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 + -# Example 1 +print("Example 1") from time import sleep from datetime import datetime def log(message, when=datetime.now()): - print('%s: %s' % (when, message)) + print(f"{when}: {message}") -log('Hi there!') +log("Hi there!") sleep(0.1) -log('Hi again!') +log("Hello again!") -# Example 2 +print("Example 2") def log(message, when=None): """Log a message with a timestamp. @@ -41,17 +68,18 @@ def log(message, when=None): when: datetime of when the message occurred. Defaults to the present time. """ - when = datetime.now() if when is None else when - print('%s: %s' % (when, message)) + if when is None: + when = datetime.now() + print(f"{when}: {message}") -# Example 3 -log('Hi there!') +print("Example 3") +log("Hi there!") sleep(0.1) -log('Hi again!') +log("Hello again!") -# Example 4 +print("Example 4") import json def decode(data, default={}): @@ -61,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. @@ -83,18 +111,19 @@ def decode(data, default=None): default: Value to return if decoding fails. Defaults to an empty dictionary. """ - if default is None: - default = {} try: return json.loads(data) except ValueError: + 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_036_example_09.py b/example_code/item_036_example_09.py new file mode 100755 index 0000000..2896986 --- /dev/null +++ b/example_code/item_036_example_09.py @@ -0,0 +1,41 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 9") +# Check types in this file with: python3 -m mypy + +from datetime import datetime +from time import sleep + +def log_typed(message: str, when: datetime | None = None) -> None: + """Log a message with a timestamp. + + Args: + message: Message to print. + when: datetime of when the message occurred. + Defaults to the present time. + """ + if when is None: + when = datetime.now() + print(f"{when}: {message}") + + +log_typed("Hi there!") +sleep(0.1) +log_typed("Hello again!") +log_typed("And one more time", when=datetime.now()) diff --git a/example_code/item_037.py b/example_code/item_037.py new file mode 100755 index 0000000..7da1cb5 --- /dev/null +++ b/example_code/item_037.py @@ -0,0 +1,263 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def safe_division( + number, + divisor, + ignore_overflow, + ignore_zero_division, +): + try: + return number / divisor + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float("inf") + else: + raise + + +print("Example 2") +result = safe_division(1.0, 10**500, True, False) +print(result) + + +print("Example 3") +result = safe_division(1.0, 0, False, True) +print(result) + + +print("Example 4") +def safe_division_b( + number, + divisor, + ignore_overflow=False, # Changed + ignore_zero_division=False, # Changed +): + try: + return number / divisor + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float("inf") + else: + raise + + +print("Example 5") +result = safe_division_b(1.0, 10**500, ignore_overflow=True) +print(result) + +result = safe_division_b(1.0, 0, ignore_zero_division=True) +print(result) + + +print("Example 6") +assert safe_division_b(1.0, 10**500, True, False) == 0 + + +print("Example 7") +def safe_division_c( + number, + divisor, + *, # Added + ignore_overflow=False, + ignore_zero_division=False, +): + try: + return number / divisor + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float("inf") + else: + raise + + +print("Example 8") +try: + safe_division_c(1.0, 10**500, True, False) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 9") +result = safe_division_c(1.0, 0, ignore_zero_division=True) +assert result == float("inf") + +try: + result = safe_division_c(1.0, 0) +except ZeroDivisionError: + pass # Expected +else: + assert False + + +print("Example 10") +assert safe_division_c(number=2, divisor=5) == 0.4 +assert safe_division_c(divisor=5, number=2) == 0.4 +assert safe_division_c(2, divisor=5) == 0.4 + + +print("Example 11") +def safe_division_d( + numerator, # Changed + denominator, # Changed + *, + ignore_overflow=False, + ignore_zero_division=False +): + try: + return numerator / denominator + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float("inf") + else: + raise + + +print("Example 12") +try: + safe_division_d(number=2, divisor=5) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 13") +def safe_division_e( + numerator, + denominator, + /, # Added + *, + ignore_overflow=False, + ignore_zero_division=False, +): + try: + return numerator / denominator + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float("inf") + else: + raise + + +print("Example 14") +assert safe_division_e(2, 5) == 0.4 + + +print("Example 15") +try: + safe_division_e(numerator=2, denominator=5) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 16") +def safe_division_f( + numerator, + denominator, + /, + ndigits=10, # Changed + *, + ignore_overflow=False, + ignore_zero_division=False, +): + try: + fraction = numerator / denominator # Changed + return round(fraction, ndigits) # Changed + except OverflowError: + if ignore_overflow: + return 0 + else: + raise + except ZeroDivisionError: + if ignore_zero_division: + return float("inf") + else: + raise + + +print("Example 17") +result = safe_division_f(22, 7) +print(result) + +result = safe_division_f(22, 7, 5) +print(result) + +result = safe_division_f(22, 7, ndigits=2) +print(result) diff --git a/example_code/item_038.py b/example_code/item_038.py new file mode 100755 index 0000000..855930c --- /dev/null +++ b/example_code/item_038.py @@ -0,0 +1,133 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def trace(func): + def wrapper(*args, **kwargs): + args_repr = repr(args) + kwargs_repr = repr(kwargs) + result = func(*args, **kwargs) + print(f"{func.__name__}" + f"({args_repr}, {kwargs_repr}) " + f"-> {result!r}") + return result + + return wrapper + + +print("Example 2") +@trace +def fibonacci(n): + """Return the n-th Fibonacci number""" + if n in (0, 1): + return n + return fibonacci(n - 2) + fibonacci(n - 1) + + +print("Example 3") +def fibonacci(n): + """Return the n-th Fibonacci number""" + if n in (0, 1): + return n + return fibonacci(n - 2) + fibonacci(n - 1) + +fibonacci = trace(fibonacci) + + +print("Example 4") +fibonacci(4) + + +print("Example 5") +print(fibonacci) + + +print("Example 6") +help(fibonacci) + + +print("Example 7") +try: + import pickle + + pickle.dumps(fibonacci) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +from functools import wraps + +def trace(func): + @wraps(func) # Changed + def wrapper(*args, **kwargs): + args_repr = repr(args) + kwargs_repr = repr(kwargs) + result = func(*args, **kwargs) + print(f"{func.__name__}" f"({args_repr}, {kwargs_repr}) " f"-> {result!r}") + return result + + return wrapper + +@trace +def fibonacci(n): + """Return the n-th Fibonacci number""" + if n in (0, 1): + return n + return fibonacci(n - 2) + fibonacci(n - 1) + + +print("Example 9") +help(fibonacci) + + +print("Example 10") +print(pickle.dumps(fibonacci)) diff --git a/example_code/item_039.py b/example_code/item_039.py new file mode 100755 index 0000000..e00eb93 --- /dev/null +++ b/example_code/item_039.py @@ -0,0 +1,132 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import math +import functools + +def log_sum(log_total, value): + log_value = math.log(value) + return log_total + log_value + +result = functools.reduce(log_sum, [10, 20, 40], 0) +print(math.exp(result)) + + +print("Example 2") +def log_sum_alt(value, log_total): # Changed + log_value = math.log(value) + return log_total + log_value + + +print("Example 3") +result = functools.reduce( + lambda total, value: log_sum_alt(value, total), # Reordered + [10, 20, 40], + 0, +) +print(math.exp(result)) + + +print("Example 4") +def log_sum_for_reduce(total, value): + return log_sum_alt(value, total) + +result = functools.reduce( + log_sum_for_reduce, + [10, 20, 40], + 0, +) +print(math.exp(result)) + + +print("Example 5") +def logn_sum(base, logn_total, value): # New first parameter + logn_value = math.log(value, base) + return logn_total + logn_value + + +print("Example 6") +result = functools.reduce( + lambda total, value: logn_sum(10, total, value), # Changed + [10, 20, 40], + 0, +) +print(math.pow(10, result)) + + +print("Example 7") +result = functools.reduce( + functools.partial(logn_sum, 10), # Changed + [10, 20, 40], + 0, +) +print(math.pow(10, result)) + + +print("Example 8") +def logn_sum_last(logn_total, value, *, base=10): # New last parameter + logn_value = math.log(value, base) + return logn_total + logn_value + + +print("Example 9") +import math + +log_sum_e = functools.partial(logn_sum_last, base=math.e) # Pinned `base` +print(log_sum_e(3, math.e**10)) + + +print("Example 10") +log_sum_e_alt = lambda *a, base=math.e, **kw: logn_sum_last(*a, base=base, **kw) +print(log_sum_e_alt(3, math.e**10)) + + +print("Example 11") +print(log_sum_e.args, log_sum_e.keywords, log_sum_e.func) diff --git a/example_code/item_04.py b/example_code/item_04.py deleted file mode 100755 index 5afff3f..0000000 --- a/example_code/item_04.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -from urllib.parse import parse_qs -my_values = parse_qs('red=5&blue=0&green=', - keep_blank_values=True) -print(repr(my_values)) - - -# Example 2 -print('Red: ', my_values.get('red')) -print('Green: ', my_values.get('green')) -print('Opacity: ', my_values.get('opacity')) - - -# Example 3 -# For query string 'red=5&blue=0&green=' -red = my_values.get('red', [''])[0] or 0 -green = my_values.get('green', [''])[0] or 0 -opacity = my_values.get('opacity', [''])[0] or 0 -print('Red: %r' % red) -print('Green: %r' % green) -print('Opacity: %r' % opacity) - - -# 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('Red: %r' % red) -print('Green: %r' % green) -print('Opacity: %r' % opacity) - - -# Example 5 -red = my_values.get('red', ['']) -red = int(red[0]) if red[0] else 0 -green = my_values.get('green', ['']) -green = int(green[0]) if green[0] else 0 -opacity = my_values.get('opacity', ['']) -opacity = int(opacity[0]) if opacity[0] else 0 -print('Red: %r' % red) -print('Green: %r' % green) -print('Opacity: %r' % opacity) - - -# Example 6 -green = my_values.get('green', ['']) -if green[0]: - green = int(green[0]) -else: - green = 0 -print('Green: %r' % green) - - -# Example 7 -def get_first_int(values, key, default=0): - found = values.get(key, ['']) - if found[0]: - found = int(found[0]) - else: - found = default - return found - - -# Example 8 -green = get_first_int(my_values, 'green') -print('Green: %r' % green) diff --git a/example_code/item_040.py b/example_code/item_040.py new file mode 100755 index 0000000..24015b5 --- /dev/null +++ b/example_code/item_040.py @@ -0,0 +1,99 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +squares = [] +for x in a: + squares.append(x**2) +print(squares) + + +print("Example 2") +squares = [x**2 for x in a] # List comprehension +print(squares) + + +print("Example 3") +alt = map(lambda x: x**2, a) +assert list(alt) == squares, f"{alt} {squares}" + + +print("Example 4") +even_squares = [x**2 for x in a if x % 2 == 0] +print(even_squares) + + +print("Example 5") +alt = map(lambda x: x**2, filter(lambda x: x % 2 == 0, a)) +assert even_squares == list(alt) + + +print("Example 6") +even_squares_dict = {x: x**2 for x in a if x % 2 == 0} +threes_cubed_set = {x**3 for x in a if x % 3 == 0} +print(even_squares_dict) +print(threes_cubed_set) + + +print("Example 7") +alt_dict = dict( + map( + lambda x: (x, x**2), + filter(lambda x: x % 2 == 0, a), + ) +) +alt_set = set( + map( + lambda x: x**3, + filter(lambda x: x % 3 == 0, a), + ) +) +assert even_squares_dict == alt_dict +assert threes_cubed_set == alt_set diff --git a/example_code/item_08.py b/example_code/item_041.py similarity index 56% rename from example_code/item_08.py rename to example_code/item_041.py index 1b5f810..8310508 100755 --- a/example_code/item_08.py +++ b/example_code/item_041.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,24 +14,55 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment +### 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() -# Example 1 -matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +matrix = [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], +] flat = [x for row in matrix for x in row] print(flat) -# 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]], @@ -42,7 +73,7 @@ print(flat) -# Example 4 +print("Example 4") flat = [] for sublist1 in my_lists: for sublist2 in sublist1: @@ -50,7 +81,7 @@ 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] @@ -60,8 +91,12 @@ 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_042.py b/example_code/item_042.py new file mode 100755 index 0000000..145b428 --- /dev/null +++ b/example_code/item_042.py @@ -0,0 +1,142 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +stock = { + "nails": 125, + "screws": 35, + "wingnuts": 8, + "washers": 24, +} + +order = ["screws", "wingnuts", "clips"] + +def get_batches(count, size): + return count // size + +result = {} +for name in order: + count = stock.get(name, 0) + batches = get_batches(count, 8) + if batches: + result[name] = batches + +print(result) + + +print("Example 2") +found = {name: get_batches(stock.get(name, 0), 8) + for name in order + if get_batches(stock.get(name, 0), 8)} +print(found) + + +print("Example 3") +has_bug = {name: get_batches(stock.get(name, 0), 4) # Wrong + for name in order + if get_batches(stock.get(name, 0), 8)} + +print("Expected:", found) +print("Found: ", has_bug) + + +print("Example 4") +found = {name: batches for name in order + if (batches := get_batches(stock.get(name, 0), 8))} +assert found == {"screws": 4, "wingnuts": 1}, found + + +print("Example 5") +try: + result = {name: (tenth := count // 10) + for name, count in stock.items() if tenth > 0} +except: + logging.exception('Expected') +else: + assert False + + +print("Example 6") +result = {name: tenth for name, count in stock.items() + if (tenth := count // 10) > 0} +print(result) + + +print("Example 7") +half = [(squared := last**2) + for count in stock.values() + if (last := count // 2) > 10] +print(f"Last item of {half} is {last} ** 2 = {squared}") + + +print("Example 8") +for count in stock.values(): + last = count // 2 + squared = last**2 + +print(f"{count} // 2 = {last}; {last} ** 2 = {squared}") + + +print("Example 9") +try: + del count + half = [count // 2 for count in stock.values()] + print(half) # Works + print(count) # Exception because loop variable didn't leak +except: + logging.exception('Expected') +else: + assert False + + +print("Example 10") +found = ((name, batches) for name in order + if (batches := get_batches(stock.get(name, 0), 8))) +print(next(found)) +print(next(found)) diff --git a/example_code/item_16.py b/example_code/item_043.py similarity index 53% rename from example_code/item_16.py rename to example_code/item_043.py index c5defa9..99fab75 100755 --- a/example_code/item_16.py +++ b/example_code/item_043.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,45 +14,78 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment +### 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 -# 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[:3]) +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)) + + +print("Example 5") result = list(index_words_iter(address)) -print(result[:3]) +print(result[:10]) -# Example 5 +print("Example 6") def index_file(handle): offset = 0 for line in handle: @@ -60,22 +93,23 @@ def index_file(handle): yield offset for letter in line: offset += 1 - if letter == ' ': + if letter == " ": yield offset -# Example 6 +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) -from itertools import islice -with open('address.txt', 'r') as f: +import itertools + +with open("address.txt", "r") as f: it = index_file(f) - results = islice(it, 0, 3) + results = itertools.islice(it, 0, 10) print(list(results)) diff --git a/example_code/item_044.py b/example_code/item_044.py new file mode 100755 index 0000000..a35ba2d --- /dev/null +++ b/example_code/item_044.py @@ -0,0 +1,77 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import random + +with open("my_file.txt", "w") as f: + for _ in range(10): + f.write("a" * random.randint(0, 100)) + f.write("\n") + +value = [len(x) for x in open("my_file.txt")] +print(value) + + +print("Example 2") +it = (len(x) for x in open("my_file.txt")) +print(it) + + +print("Example 3") +print(next(it)) +print(next(it)) + + +print("Example 4") +roots = ((x, x**0.5) for x in it) + + +print("Example 5") +print(next(roots)) diff --git a/example_code/item_045.py b/example_code/item_045.py new file mode 100755 index 0000000..344fedd --- /dev/null +++ b/example_code/item_045.py @@ -0,0 +1,88 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def move(period, speed): + for _ in range(period): + yield speed + +def pause(delay): + for _ in range(delay): + yield 0 + + +print("Example 2") +def animate(): + for delta in move(4, 5.0): + yield delta + for delta in pause(3): + yield delta + for delta in move(2, 3.0): + yield delta + + +print("Example 3") +def render(delta): + print(f"Delta: {delta:.1f}") + # Move the images onscreen + +def run(func): + for delta in func(): + render(delta) + +run(animate) + + +print("Example 4") +def animate_composed(): + yield from move(4, 5.0) + yield from pause(3) + yield from move(2, 3.0) + +run(animate_composed) diff --git a/example_code/item_046.py b/example_code/item_046.py new file mode 100755 index 0000000..b7a33de --- /dev/null +++ b/example_code/item_046.py @@ -0,0 +1,171 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import math + +def wave(amplitude, steps): + step_size = 2 * math.pi / steps + for step in range(steps): + radians = step * step_size + fraction = math.sin(radians) + output = amplitude * fraction + yield output + + +print("Example 2") +def transmit(output): + if output is None: + print(f"Output is None") + else: + print(f"Output: {output:>5.1f}") + +def run(it): + for output in it: + transmit(output) + +run(wave(3.0, 8)) + + +print("Example 3") +def my_generator(): + received = yield 1 + print(f"{received=}") + +it = my_generator() +output = next(it) # Get first generator output +print(f"{output=}") + +try: + next(it) # Run generator until it exits +except StopIteration: + pass +else: + assert False + + +print("Example 4") +it = my_generator() +output = it.send(None) # Get first generator output +print(f"{output=}") + +try: + it.send("hello!") # Send value into the generator +except StopIteration: + pass +else: + assert False + + +print("Example 5") +def wave_modulating(steps): + step_size = 2 * math.pi / steps + amplitude = yield # Receive initial amplitude + for step in range(steps): + radians = step * step_size + fraction = math.sin(radians) + output = amplitude * fraction + amplitude = yield output # Receive next amplitude + + +print("Example 6") +def run_modulating(it): + amplitudes = [None, 7, 7, 7, 2, 2, 2, 2, 10, 10, 10, 10, 10] + for amplitude in amplitudes: + output = it.send(amplitude) + transmit(output) + +run_modulating(wave_modulating(12)) + + +print("Example 7") +def complex_wave(): + yield from wave(7.0, 3) + yield from wave(2.0, 4) + yield from wave(10.0, 5) + +run(complex_wave()) + + +print("Example 8") +def complex_wave_modulating(): + yield from wave_modulating(3) + yield from wave_modulating(4) + yield from wave_modulating(5) + +run_modulating(complex_wave_modulating()) + + +print("Example 9") +def wave_cascading(amplitude_it, steps): + step_size = 2 * math.pi / steps + for step in range(steps): + radians = step * step_size + fraction = math.sin(radians) + amplitude = next(amplitude_it) # Get next input + output = amplitude * fraction + yield output + + +print("Example 10") +def complex_wave_cascading(amplitude_it): + yield from wave_cascading(amplitude_it, 3) + yield from wave_cascading(amplitude_it, 4) + yield from wave_cascading(amplitude_it, 5) + + +print("Example 11") +def run_cascading(): + amplitudes = [7, 7, 7, 2, 2, 2, 2, 10, 10, 10, 10, 10] + it = complex_wave_cascading(iter(amplitudes)) # Supplies iterator + for amplitude in amplitudes: + output = next(it) + transmit(output) + +run_cascading() diff --git a/example_code/item_047.py b/example_code/item_047.py new file mode 100755 index 0000000..1cc5a1a --- /dev/null +++ b/example_code/item_047.py @@ -0,0 +1,177 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + class MyError(Exception): + pass + + def my_generator(): + yield 1 + yield 2 + yield 3 + + it = my_generator() + print(next(it)) # Yields 1 + print(next(it)) # Yields 2 + print(it.throw(MyError("test error"))) # Raises +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +def my_generator(): + yield 1 + + try: + yield 2 + except MyError: + print("Got MyError!") + else: + yield 3 + + yield 4 + +it = my_generator() +print(next(it)) # Yields 1 +print(next(it)) # Yields 2 +print(it.throw(MyError("test error"))) # Yields 4 + + +print("Example 3") +class Reset(Exception): + pass + +def timer(period): + current = period + while current: + try: + yield current + except Reset: + print("Resetting") + current = period + else: + current -= 1 + + +print("Example 4") +ORIGINAL_RESETS = [ + False, + False, + False, + True, + False, + True, + False, + False, + False, + False, + False, + False, + False, + False, +] +RESETS = ORIGINAL_RESETS[:] + +def check_for_reset(): + # Poll for external event + return RESETS.pop(0) + +def announce(remaining): + print(f"{remaining} ticks remaining") + +def run(): + it = timer(4) + while True: + try: + if check_for_reset(): + current = it.throw(Reset()) + else: + current = next(it) + except StopIteration: + break + else: + announce(current) + +run() + + +print("Example 5") +class Timer: + def __init__(self, period): + self.current = period + self.period = period + + def reset(self): + print("Resetting") + self.current = self.period + + def tick(self): + before = self.current + self.current -= 1 + return before + + def __bool__(self): + return self.current > 0 + + +print("Example 6") +RESETS = ORIGINAL_RESETS[:] + +def run(): + timer = Timer(4) + while timer: + if check_for_reset(): + timer.reset() + + announce(timer.tick()) + +run() diff --git a/example_code/item_23.py b/example_code/item_048.py similarity index 59% rename from example_code/item_23.py rename to example_code/item_048.py index 45a5c22..902b8fc 100755 --- a/example_code/item_23.py +++ b/example_code/item_048.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,41 +14,68 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment +### 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 -# Example 1 -names = ['Socrates', 'Archimedes', 'Plato', 'Aristotle'] -names.sort(key=lambda x: len(x)) -print(names) +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +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 -# Example 2 -from collections import defaultdict +print("Example 1") +names = ["Socrates", "Archimedes", "Plato", "Aristotle"] +names.sort(key=len) +print(names) + + +print("Example 2") def log_missing(): - print('Key added') + print("Key added") return 0 -# Example 3 -current = {'green': 12, 'blue': 3} +print("Example 3") +from collections import defaultdict + +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 @@ -64,14 +91,14 @@ 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 -class CountMissing(object): +print("Example 6") +class CountMissing: def __init__(self): self.added = 0 @@ -80,17 +107,17 @@ def missing(self): return 0 -# Example 7 +print("Example 7") counter = CountMissing() -result = defaultdict(counter.missing, current) # Method reference +result = defaultdict(counter.missing, current) # Method ref for key, amount in increments: result[key] += amount assert counter.added == 2 print(result) -# Example 8 -class BetterCountMissing(object): +print("Example 8") +class BetterCountMissing: def __init__(self): self.added = 0 @@ -99,11 +126,11 @@ def __call__(self): return 0 counter = BetterCountMissing() -counter() +assert counter() == 0 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_05.py b/example_code/item_05.py deleted file mode 100755 index 4bf20ae..0000000 --- a/example_code/item_05.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] -print('First four:', a[:4]) -print('Last four: ', a[-4:]) -print('Middle two:', a[3:-3]) - - -# Example 2 -assert a[:5] == a[0:5] - - -# Example 3 -assert a[5:] == a[5:len(a)] - - -# Example 4 -print(a[:5]) -print(a[:-1]) -print(a[4:]) -print(a[-3:]) -print(a[2:5]) -print(a[2:-1]) -print(a[-3:-1]) - - -# Example 5 -a[:] # ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] -a[:5] # ['a', 'b', 'c', 'd', 'e'] -a[:-1] # ['a', 'b', 'c', 'd', 'e', 'f', 'g'] -a[4:] # ['e', 'f', 'g', 'h'] -a[-3:] # ['f', 'g', 'h'] -a[2:5] # ['c', 'd', 'e'] -a[2:-1] # ['c', 'd', 'e', 'f', 'g'] -a[-3:-1] # ['f', 'g'] - - -# Example 6 -first_twenty_items = a[:20] -last_twenty_items = a[-20:] - - -# Example 7 -try: - a[20] -except: - logging.exception('Expected') -else: - assert False - - -# Example 8 -b = a[4:] -print('Before: ', b) -b[1] = 99 -print('After: ', b) -print('No change:', a) - - -# Example 9 -print('Before ', a) -a[2:7] = [99, 22, 14] -print('After ', a) - - -# Example 10 -b = a[:] -assert b == a and b is not a - - -# Example 11 -b = a -print('Before', a) -a[:] = [101, 102, 103] -assert a is b # Still the same list object -print('After ', a) # Now has different contents 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_051_example_05.py b/example_code/item_051_example_05.py new file mode 100755 index 0000000..1d51199 --- /dev/null +++ b/example_code/item_051_example_05.py @@ -0,0 +1,32 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 5") +# Check types in this file with: python3 -m mypy + +class RGB: + def __init__( + self, red: int, green: int, blue: int + ) -> None: # Changed + self.red = red + self.green = green + self.blue = blue + + +obj = RGB(1, "bad", 3) +obj.red = "also bad" diff --git a/example_code/item_24.py b/example_code/item_052.py similarity index 61% rename from example_code/item_24.py rename to example_code/item_052.py index 3d82ee0..acffdf5 100755 --- a/example_code/item_24.py +++ b/example_code/item_052.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,30 +14,58 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment +### 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) -# Example 1 -class InputData(object): +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class InputData: def read(self): raise NotImplementedError -# Example 2 +print("Example 2") class PathInputData(InputData): def __init__(self, path): super().__init__() self.path = path def read(self): - return open(self.path).read() + with open(self.path) as f: + return f.read() -# Example 3 -class Worker(object): +print("Example 3") +class Worker: def __init__(self, input_data): self.input_data = input_data self.result = None @@ -49,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): @@ -67,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: @@ -75,45 +103,48 @@ 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[0], workers[1:] + first, *rest = workers for worker in rest: first.reduce(worker) 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 -from tempfile import TemporaryDirectory +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)) -with TemporaryDirectory() as tmpdir: - write_test_files(tmpdir) - result = mapreduce(tmpdir) +tmpdir = "test_inputs" +write_test_files(tmpdir) -print('There are', result, 'lines') +result = mapreduce(tmpdir) +print(f"There are {result} lines") -# Example 10 -class GenericInputData(object): +print("Example 10") +class GenericInputData: def read(self): raise NotImplementedError @@ -122,24 +153,26 @@ def generate_inputs(cls, config): raise NotImplementedError -# Example 11 +print("Example 11") class PathInputData(GenericInputData): def __init__(self, path): super().__init__() self.path = path def read(self): - return open(self.path).read() + 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 -class GenericWorker(object): +print("Example 12") +class GenericWorker: def __init__(self, input_data): self.input_data = input_data self.result = None @@ -158,25 +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 -with TemporaryDirectory() as tmpdir: - write_test_files(tmpdir) - config = {'data_dir': tmpdir} - result = mapreduce(LineCountWorker, PathInputData, config) -print('There are', result, 'lines') +print("Example 15") +config = {"data_dir": tmpdir} +result = mapreduce(LineCountWorker, PathInputData, config) +print(f"There are {result} lines") diff --git a/example_code/item_053.py b/example_code/item_053.py new file mode 100755 index 0000000..59495f5 --- /dev/null +++ b/example_code/item_053.py @@ -0,0 +1,170 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class MyBaseClass: + def __init__(self, value): + self.value = value + +class MyChildClass(MyBaseClass): + def __init__(self): + MyBaseClass.__init__(self, 5) + + +print("Example 2") +class TimesTwo: + def __init__(self): + self.value *= 2 + +class PlusFive: + def __init__(self): + self.value += 5 + + +print("Example 3") +class OneWay(MyBaseClass, TimesTwo, PlusFive): + def __init__(self, value): + MyBaseClass.__init__(self, value) + TimesTwo.__init__(self) + PlusFive.__init__(self) + + +print("Example 4") +foo = OneWay(5) +print("First ordering value is (5 * 2) + 5 =", foo.value) + + +print("Example 5") +class AnotherWay(MyBaseClass, PlusFive, TimesTwo): + def __init__(self, value): + MyBaseClass.__init__(self, value) + TimesTwo.__init__(self) + PlusFive.__init__(self) + + +print("Example 6") +bar = AnotherWay(5) +print("Second ordering should be (5 + 5) * 2, but is", bar.value) + + +print("Example 7") +class TimesSeven(MyBaseClass): + def __init__(self, value): + MyBaseClass.__init__(self, value) + self.value *= 7 + +class PlusNine(MyBaseClass): + def __init__(self, value): + MyBaseClass.__init__(self, value) + self.value += 9 + + +print("Example 8") +class ThisWay(TimesSeven, PlusNine): + def __init__(self, value): + TimesSeven.__init__(self, value) + PlusNine.__init__(self, value) + +foo = ThisWay(5) +print("Should be (5 * 7) + 9 = 44 but is", foo.value) + + +print("Example 9") +class MyBaseClass: + def __init__(self, value): + self.value = value + +class TimesSevenCorrect(MyBaseClass): + def __init__(self, value): + super().__init__(value) + self.value *= 7 + +class PlusNineCorrect(MyBaseClass): + def __init__(self, value): + super().__init__(value) + self.value += 9 + + +print("Example 10") +class GoodWay(TimesSevenCorrect, PlusNineCorrect): + def __init__(self, value): + super().__init__(value) + +foo = GoodWay(5) +print("Should be 7 * (5 + 9) = 98 and is", foo.value) + + +print("Example 11") +mro_str = "\n".join(repr(cls) for cls in GoodWay.__mro__) +print(mro_str) + + +print("Example 12") +class ExplicitTrisect(MyBaseClass): + def __init__(self, value): + super(ExplicitTrisect, self).__init__(value) + self.value /= 3 + +assert ExplicitTrisect(9).value == 3 + + +print("Example 13") +class AutomaticTrisect(MyBaseClass): + def __init__(self, value): + super(__class__, self).__init__(value) + self.value /= 3 + +class ImplicitTrisect(MyBaseClass): + def __init__(self, value): + super().__init__(value) + self.value /= 3 + +assert ExplicitTrisect(9).value == 3 +assert AutomaticTrisect(9).value == 3 +assert ImplicitTrisect(9).value == 3 diff --git a/example_code/item_26.py b/example_code/item_054.py similarity index 72% rename from example_code/item_26.py rename to example_code/item_054.py index 77f83f5..4e6acfe 100755 --- a/example_code/item_26.py +++ b/example_code/item_054.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,19 +14,44 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment +### 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() -# Example 1 -class ToDictMixin(object): +atexit.register(close_open_files) +### End book environment setup + + +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(): @@ -40,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 @@ -54,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) @@ -91,23 +123,23 @@ 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(object): +class JsonMixin: @classmethod def from_json(cls, data): kwargs = json.loads(data) @@ -117,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) @@ -136,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_27.py b/example_code/item_055.py similarity index 59% rename from example_code/item_27.py rename to example_code/item_055.py index 512434d..5cb93a7 100755 --- a/example_code/item_27.py +++ b/example_code/item_055.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,14 +14,41 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment +### 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 -# Example 1 -class MyObject(object): + +print("Example 1") +class MyObject: def __init__(self): self.public_field = 5 self.__private_field = 10 @@ -30,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: @@ -48,8 +75,8 @@ def get_private_field(self): assert False -# Example 5 -class MyOtherObject(object): +print("Example 5") +class MyOtherObject: def __init__(self): self.__private_field = 71 @@ -61,9 +88,9 @@ 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(object): + class MyParentObject: def __init__(self): self.__private_field = 71 @@ -79,53 +106,53 @@ 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 -class MyClass(object): +print("Example 9") +class MyStringClass: def __init__(self, value): self.__value = value def get_value(self): return str(self.__value) -foo = MyClass(5) -assert foo.get_value() == '5' +foo = MyStringClass(5) +assert foo.get_value() == "5" -# Example 10 -class MyIntegerSubclass(MyClass): +print("Example 10") +class MyIntegerSubclass(MyStringClass): def get_value(self): - return int(self._MyClass__value) + return int(self._MyStringClass__value) -foo = MyIntegerSubclass(5) +foo = MyIntegerSubclass("5") assert foo.get_value() == 5 -# Example 11 -class MyBaseClass(object): +print("Example 11") +class MyBaseClass: def __init__(self, value): self.__value = value def get_value(self): return self.__value -class MyClass(MyBaseClass): +class MyStringClass(MyBaseClass): def get_value(self): - return str(super().get_value()) + return str(super().get_value()) # Updated -class MyIntegerSubclass(MyClass): +class MyIntegerSubclass(MyStringClass): def get_value(self): - return int(self._MyClass__value) + return int(self._MyStringClass__value) # Not updated -# Example 12 +print("Example 12") try: foo = MyIntegerSubclass(5) foo.get_value() @@ -135,18 +162,20 @@ def get_value(self): assert False -# Example 13 -class MyClass(object): +print("Example 13") +class MyStringClass: def __init__(self, value): # This stores the user-supplied value for the object. - # It should be coercible to a string. Once assigned for + # It should be coercible to a string. Once assigned in # the object it should be treated as immutable. self._value = value + def get_value(self): return str(self._value) -class MyIntegerSubclass(MyClass): + +class MyIntegerSubclass(MyStringClass): def get_value(self): return self._value @@ -154,8 +183,8 @@ def get_value(self): assert foo.get_value() == 5 -# Example 14 -class ApiClass(object): +print("Example 14") +class ApiClass: def __init__(self): self._value = 5 @@ -165,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(a.get(), 'and', a._value, 'should be different') +print(f"{a.get()} and {a._value} should be different") -# Example 15 -class ApiClass(object): +print("Example 15") +class ApiClass: def __init__(self): - self.__value = 5 + self.__value = 5 # Double underscore def get(self): - return self.__value + return self.__value # Double underscore class Child(ApiClass): def __init__(self): super().__init__() - self._value = 'hello' # OK! + self._value = "hello" # OK! a = Child() -print(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_057.py b/example_code/item_057.py new file mode 100755 index 0000000..5f60b20 --- /dev/null +++ b/example_code/item_057.py @@ -0,0 +1,202 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class FrequencyList(list): + def __init__(self, members): + super().__init__(members) + + def frequency(self): + counts = {} + for item in self: + counts[item] = counts.get(item, 0) + 1 + return counts + + +print("Example 2") +foo = FrequencyList(["a", "b", "a", "c", "b", "a", "d"]) +print("Length is", len(foo)) +foo.pop() # Removes "d" +print("After pop:", repr(foo)) +print("Frequency:", foo.frequency()) + + +print("Example 3") +class BinaryNode: + def __init__(self, value, left=None, right=None): + self.value = value + self.left = left + self.right = right + + +print("Example 4") +bar = [1, 2, 3] +bar[0] + + +print("Example 5") +bar.__getitem__(0) + + +print("Example 6") +class IndexableNode(BinaryNode): + def _traverse(self): + if self.left is not None: + yield from self.left._traverse() + yield self + if self.right is not None: + yield from self.right._traverse() + + def __getitem__(self, index): + for i, item in enumerate(self._traverse()): + if i == index: + return item.value + raise IndexError(f"Index {index} is out of range") + + +print("Example 7") +tree = IndexableNode( + 10, + left=IndexableNode( + 5, + left=IndexableNode(2), + right=IndexableNode(6, right=IndexableNode(7)), + ), + right=IndexableNode(15, left=IndexableNode(11)), +) + + +print("Example 8") +print("LRR is", tree.left.right.right.value) +print("Index 0 is", tree[0]) +print("Index 1 is", tree[1]) +print("11 in the tree?", 11 in tree) +print("17 in the tree?", 17 in tree) +print("Tree is", list(tree)) + +try: + tree[100] +except IndexError: + pass +else: + assert False + + +print("Example 9") +try: + len(tree) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 10") +class SequenceNode(IndexableNode): + def __len__(self): + count = 0 + for _ in self._traverse(): + count += 1 + return count + + +print("Example 11") +tree = SequenceNode( + 10, + left=SequenceNode( + 5, + left=SequenceNode(2), + right=SequenceNode(6, right=SequenceNode(7)), + ), + right=SequenceNode(15, left=SequenceNode(11)), +) + +print("Tree length is", len(tree)) + + +print("Example 12") +try: + # Make sure that this doesn't work + tree.count(4) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 13") +try: + from collections.abc import Sequence + + class BadType(Sequence): + pass + + foo = BadType() +except: + logging.exception('Expected') +else: + assert False + + +print("Example 14") +class BetterNode(SequenceNode, Sequence): + pass + +tree = BetterNode( + 10, + left=BetterNode( + 5, + left=BetterNode(2), + right=BetterNode(6, right=BetterNode(7)), + ), + right=BetterNode(15, left=BetterNode(11)), +) + +print("Index of 7 is", tree.index(7)) +print("Count of 10 is", tree.count(10)) diff --git a/example_code/item_29.py b/example_code/item_058.py similarity index 60% rename from example_code/item_29.py rename to example_code/item_058.py index cade8bd..776d49e 100755 --- a/example_code/item_29.py +++ b/example_code/item_058.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,14 +14,41 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment +### 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 + -# Example 1 -class OldResistor(object): +print("Example 1") +class OldResistor: def __init__(self, ohms): self._ohms = ohms @@ -32,19 +59,20 @@ def set_ohms(self, ohms): self._ohms = ohms -# Example 2 +print("Example 2") r0 = OldResistor(50e3) -print('Before: %5r' % r0.get_ohms()) +print("Before:", r0.get_ohms()) r0.set_ohms(10e3) -print('After: %5r' % r0.get_ohms()) +print("After: ", r0.get_ohms()) -# Example 3 -r0.set_ohms(r0.get_ohms() + 5e3) +print("Example 3") +r0.set_ohms(r0.get_ohms() - 4e3) +assert r0.get_ohms() == 6e3 -# Example 4 -class Resistor(object): +print("Example 4") +class Resistor: def __init__(self, ohms): self.ohms = ohms self.voltage = 0 @@ -52,15 +80,16 @@ def __init__(self, ohms): r1 = Resistor(50e3) r1.ohms = 10e3 -print('%r ohms, %r volts, %r amps' % - (r1.ohms, r1.voltage, r1.current)) +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) @@ -76,14 +105,14 @@ def voltage(self, voltage): self.current = self._voltage / self.ohms -# Example 7 -r2 = VoltageResistance(1e3) -print('Before: %5r amps' % r2.current) +print("Example 7") +r2 = VoltageResistance(1e2) +print(f"Before: {r2.current:.2f} amps") r2.voltage = 10 -print('After: %5r amps' % r2.current) +print(f"After: {r2.current:.2f} amps") -# Example 8 +print("Example 8") class BoundedResistance(Resistor): def __init__(self, ohms): super().__init__(ohms) @@ -95,11 +124,11 @@ def ohms(self): @ohms.setter def ohms(self, ohms): if ohms <= 0: - raise ValueError('%f ohms must be > 0' % 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 @@ -109,7 +138,7 @@ def ohms(self, ohms): assert False -# Example 10 +print("Example 10") try: BoundedResistance(-5) except: @@ -118,7 +147,7 @@ def ohms(self, ohms): assert False -# Example 11 +print("Example 11") class FixedResistance(Resistor): def __init__(self, ohms): super().__init__(ohms) @@ -129,12 +158,12 @@ def ohms(self): @ohms.setter def ohms(self, ohms): - if hasattr(self, '_ohms'): - raise AttributeError("Can't set attribute") + if hasattr(self, "_ohms"): + raise AttributeError("Ohms is immutable") self._ohms = ohms -# Example 12 +print("Example 12") try: r4 = FixedResistance(1e3) r4.ohms = 2e3 @@ -144,7 +173,7 @@ def ohms(self, ohms): assert False -# Example 13 +print("Example 13") class MysteriousResistor(Resistor): @property def ohms(self): @@ -156,9 +185,9 @@ def ohms(self, ohms): self._ohms = ohms -# Example 14 +print("Example 14") r7 = MysteriousResistor(10) -r7.current = 0.01 -print('Before: %5r' % r7.voltage) +r7.current = 0.1 +print(f"Before: {r7.voltage:.2f}") r7.ohms -print('After: %5r' % r7.voltage) +print(f"After: {r7.voltage:.2f}") diff --git a/example_code/item_059.py b/example_code/item_059.py new file mode 100755 index 0000000..267a8f7 --- /dev/null +++ b/example_code/item_059.py @@ -0,0 +1,210 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +from datetime import datetime, timedelta + +class Bucket: + def __init__(self, period): + self.period_delta = timedelta(seconds=period) + self.reset_time = datetime.now() + self.quota = 0 + + def __repr__(self): + return f"Bucket(quota={self.quota})" + + +bucket = Bucket(60) +print(bucket) + + +print("Example 2") +def fill(bucket, amount): + now = datetime.now() + if (now - bucket.reset_time) > bucket.period_delta: + bucket.quota = 0 + bucket.reset_time = now + bucket.quota += amount + + +print("Example 3") +def deduct(bucket, amount): + now = datetime.now() + if (now - bucket.reset_time) > bucket.period_delta: + return False # Bucket hasn't been filled this period + if bucket.quota - amount < 0: + return False # Bucket was filled, but not enough + bucket.quota -= amount + return True # Bucket had enough, quota consumed + + +print("Example 4") +bucket = Bucket(60) +fill(bucket, 100) +print(bucket) + + +print("Example 5") +if deduct(bucket, 99): + print("Had 99 quota") +else: + print("Not enough for 99 quota") + +print(bucket) + + +print("Example 6") +if deduct(bucket, 3): + print("Had 3 quota") +else: + print("Not enough for 3 quota") + +print(bucket) + + +print("Example 7") +class NewBucket: + def __init__(self, period): + self.period_delta = timedelta(seconds=period) + self.reset_time = datetime.now() + self.max_quota = 0 + self.quota_consumed = 0 + + def __repr__(self): + return ( + f"NewBucket(max_quota={self.max_quota}, " + f"quota_consumed={self.quota_consumed})" + ) + + + print("Example 8") + @property + def quota(self): + return self.max_quota - self.quota_consumed + + + print("Example 9") + @quota.setter + def quota(self, amount): + delta = self.max_quota - amount + if amount == 0: + # Quota being reset for a new period + self.quota_consumed = 0 + self.max_quota = 0 + elif delta < 0: + # Quota being filled during the period + self.max_quota = amount + self.quota_consumed + else: + # Quota being consumed during the period + self.quota_consumed = delta + + +print("Example 10") +bucket = NewBucket(60) +print("Initial", bucket) +fill(bucket, 100) +print("Filled", bucket) + +if deduct(bucket, 99): + print("Had 99 quota") +else: + print("Not enough for 99 quota") + +print("Now", bucket) + +if deduct(bucket, 3): + print("Had 3 quota") +else: + print("Not enough for 3 quota") + +print("Still", bucket) + + +print("Example 11") +bucket = NewBucket(6000) +assert bucket.max_quota == 0 +assert bucket.quota_consumed == 0 +assert bucket.quota == 0 + +fill(bucket, 100) +assert bucket.max_quota == 100 +assert bucket.quota_consumed == 0 +assert bucket.quota == 100 + +assert deduct(bucket, 10) +assert bucket.max_quota == 100 +assert bucket.quota_consumed == 10 +assert bucket.quota == 90 + +assert deduct(bucket, 20) +assert bucket.max_quota == 100 +assert bucket.quota_consumed == 30 +assert bucket.quota == 70 + +fill(bucket, 50) +assert bucket.max_quota == 150 +assert bucket.quota_consumed == 30 +assert bucket.quota == 120 + +assert deduct(bucket, 40) +assert bucket.max_quota == 150 +assert bucket.quota_consumed == 70 +assert bucket.quota == 80 + +assert not deduct(bucket, 81) +assert bucket.max_quota == 150 +assert bucket.quota_consumed == 70 +assert bucket.quota == 80 + +bucket.reset_time += bucket.period_delta - timedelta(1) +assert bucket.quota == 80 +assert not deduct(bucket, 79) + +fill(bucket, 1) +assert bucket.quota == 1 diff --git a/example_code/item_06.py b/example_code/item_06.py deleted file mode 100755 index bdab1d7..0000000 --- a/example_code/item_06.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -a = ['red', 'orange', 'yellow', 'green', 'blue', 'purple'] -odds = a[::2] -evens = a[1::2] -print(odds) -print(evens) - - -# Example 2 -x = b'mongoose' -y = x[::-1] -print(y) - - -# Example 3 -try: - w = '謝謝' - x = w.encode('utf-8') - y = x[::-1] - z = y.decode('utf-8') -except: - logging.exception('Expected') -else: - assert False - - -# Example 4 -a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] -a[::2] # ['a', 'c', 'e', 'g'] -a[::-2] # ['h', 'f', 'd', 'b'] - - -# Example 5 -a[2::2] # ['c', 'e', 'g'] -a[-2::-2] # ['g', 'e', 'c', 'a'] -a[-2:2:-2] # ['g', 'e'] -a[2:2:-2] # [] - - -# Example 6 -b = a[::2] # ['a', 'c', 'e', 'g'] -c = b[1:-1] # ['c', 'e'] -print(a) -print(b) -print(c) diff --git a/example_code/item_060.py b/example_code/item_060.py new file mode 100755 index 0000000..a1bf196 --- /dev/null +++ b/example_code/item_060.py @@ -0,0 +1,233 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class Homework: + def __init__(self): + self._grade = 0 + + @property + def grade(self): + return self._grade + + @grade.setter + def grade(self, value): + if not (0 <= value <= 100): + raise ValueError("Grade must be between 0 and 100") + self._grade = value + + +print("Example 2") +galileo = Homework() +galileo.grade = 95 +assert galileo.grade == 95 + + +print("Example 3") +class Exam: + def __init__(self): + self._writing_grade = 0 + self._math_grade = 0 + + @staticmethod + def _check_grade(value): + if not (0 <= value <= 100): + raise ValueError("Grade must be between 0 and 100") + + + print("Example 4") + @property + def writing_grade(self): + return self._writing_grade + + @writing_grade.setter + def writing_grade(self, value): + self._check_grade(value) + self._writing_grade = value + + @property + def math_grade(self): + return self._math_grade + + @math_grade.setter + def math_grade(self, value): + self._check_grade(value) + self._math_grade = value + +galileo = Exam() +galileo.writing_grade = 85 +galileo.math_grade = 99 + +assert galileo.writing_grade == 85 +assert galileo.math_grade == 99 + + +print("Example 5") +class Grade: + def __get__(self, instance, instance_type): + pass + + def __set__(self, instance, value): + pass + +class Exam: + # Class attributes + math_grade = Grade() + writing_grade = Grade() + science_grade = Grade() + + +print("Example 6") +exam = Exam() +exam.writing_grade = 40 + + +print("Example 7") +Exam.__dict__["writing_grade"].__set__(exam, 40) + + +print("Example 8") +exam.writing_grade + + +print("Example 9") +Exam.__dict__["writing_grade"].__get__(exam, Exam) + + +print("Example 10") +class Grade: + def __init__(self): + self._value = 0 + + def __get__(self, instance, instance_type): + return self._value + + def __set__(self, instance, value): + if not (0 <= value <= 100): + raise ValueError("Grade must be between 0 and 100") + self._value = value + + +print("Example 11") +class Exam: + math_grade = Grade() + writing_grade = Grade() + science_grade = Grade() + +first_exam = Exam() +first_exam.writing_grade = 82 +first_exam.science_grade = 99 +print("Writing", first_exam.writing_grade) +print("Science", first_exam.science_grade) + + +print("Example 12") +second_exam = Exam() +second_exam.writing_grade = 75 +print(f"Second {second_exam.writing_grade} is right") +print(f"First {first_exam.writing_grade} is wrong; " f"should be 82") + + +print("Example 13") +class DictGrade: + def __init__(self): + self._values = {} + + def __get__(self, instance, instance_type): + if instance is None: + return self + return self._values.get(instance, 0) + + def __set__(self, instance, value): + if not (0 <= value <= 100): + raise ValueError("Grade must be between 0 and 100") + self._values[instance] = value + +class DictExam: + math_grade = DictGrade() + writing_grade = DictGrade() + science_grade = DictGrade() + +first_exam = DictExam() +first_exam.math_grade = 78 +second_exam = DictExam() +second_exam.math_grade = 89 +print(first_exam.math_grade) +print(second_exam.math_grade) + + +print("Example 14") +class NamedGrade: + def __set_name__(self, owner, name): + self.internal_name = "_" + name + + + print("Example 15") + def __get__(self, instance, instance_type): + if instance is None: + return self + return getattr(instance, self.internal_name) + + def __set__(self, instance, value): + if not (0 <= value <= 100): + raise ValueError("Grade must be between 0 and 100") + setattr(instance, self.internal_name, value) + + +print("Example 16") +class NamedExam: + math_grade = NamedGrade() + writing_grade = NamedGrade() + science_grade = NamedGrade() + +first_exam = NamedExam() +first_exam.math_grade = 78 +first_exam.writing_grade = 89 +first_exam.science_grade = 94 +print(first_exam.__dict__) diff --git a/example_code/item_061.py b/example_code/item_061.py new file mode 100755 index 0000000..7619deb --- /dev/null +++ b/example_code/item_061.py @@ -0,0 +1,201 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class LazyRecord: + def __init__(self): + self.exists = 5 + + def __getattr__(self, name): + value = f"Value for {name}" + setattr(self, name, value) + return value + + +print("Example 2") +data = LazyRecord() +print("Before:", data.__dict__) +print("foo: ", data.foo) +print("After: ", data.__dict__) + + +print("Example 3") +class LoggingLazyRecord(LazyRecord): + def __getattr__(self, name): + print( + f"* Called __getattr__({name!r}), " + f"populating instance dictionary" + ) + result = super().__getattr__(name) + print(f"* Returning {result!r}") + return result + +data = LoggingLazyRecord() +print("exists: ", data.exists) +print("First foo: ", data.foo) +print("Second foo: ", data.foo) + + +print("Example 4") +class ValidatingRecord: + def __init__(self): + self.exists = 5 + + def __getattribute__(self, name): + print(f"* Called __getattribute__({name!r})") + try: + value = super().__getattribute__(name) + print(f"* Found {name!r}, returning {value!r}") + return value + except AttributeError: + value = f"Value for {name}" + print(f"* Setting {name!r} to {value!r}") + setattr(self, name, value) + return value + +data = ValidatingRecord() +print("exists: ", data.exists) +print("First foo: ", data.foo) +print("Second foo: ", data.foo) + + +print("Example 5") +try: + class MissingPropertyRecord: + def __getattr__(self, name): + if name == "bad_name": + raise AttributeError(f"{name} is missing") + value = f"Value for {name}" + setattr(self, name, value) + return value + + data = MissingPropertyRecord() + assert data.foo == "Value for foo" # Test this works + data.bad_name +except: + logging.exception('Expected') +else: + assert False + + +print("Example 6") +data = LoggingLazyRecord() # Implements __getattr__ +print("Before: ", data.__dict__) +print("Has first foo: ", hasattr(data, "foo")) +print("After: ", data.__dict__) +print("Has second foo: ", hasattr(data, "foo")) + + +print("Example 7") +data = ValidatingRecord() # Implements __getattribute__ +print("Has first foo: ", hasattr(data, "foo")) +print("Has second foo: ", hasattr(data, "foo")) + + +print("Example 8") +class SavingRecord: + def __setattr__(self, name, value): + # Save some data for the record + pass + super().__setattr__(name, value) + + +print("Example 9") +class LoggingSavingRecord(SavingRecord): + def __setattr__(self, name, value): + print(f"* Called __setattr__({name!r}, {value!r})") + super().__setattr__(name, value) + +data = LoggingSavingRecord() +print("Before: ", data.__dict__) +data.foo = 5 +print("After: ", data.__dict__) +data.foo = 7 +print("Finally:", data.__dict__) + + +print("Example 10") +class BrokenDictionaryRecord: + def __init__(self, data): + self._data = data + + def __getattribute__(self, name): + print(f"* Called __getattribute__({name!r})") + return self._data[name] + + +print("Example 11") +try: + import sys + + sys.setrecursionlimit(50) + data = BrokenDictionaryRecord({"foo": 3}) + data.foo +except: + logging.exception('Expected') +else: + assert False + + +print("Example 12") +class DictionaryRecord: + def __init__(self, data): + self._data = data + + def __getattribute__(self, name): + # Prevent weird interactions with isinstance() used + # by example code harness. + if name == "__class__": + return DictionaryRecord + print(f"* Called __getattribute__({name!r})") + data_dict = super().__getattribute__("_data") + return data_dict[name] + +data = DictionaryRecord({"foo": 3}) +print("foo: ", data.foo) diff --git a/example_code/item_062.py b/example_code/item_062.py new file mode 100755 index 0000000..4a3992a --- /dev/null +++ b/example_code/item_062.py @@ -0,0 +1,304 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class Meta(type): + def __new__(meta, name, bases, class_dict): + global print + orig_print = print + print(f"* Running {meta}.__new__ for {name}") + print("Bases:", bases) + print = pprint + print(class_dict) + print = orig_print + return type.__new__(meta, name, bases, class_dict) + +class MyClass(metaclass=Meta): + stuff = 123 + + def foo(self): + pass + +class MySubclass(MyClass): + other = 567 + + def bar(self): + pass + + +print("Example 2") +class ValidatePolygon(type): + def __new__(meta, name, bases, class_dict): + # Only validate subclasses of the Polygon class + if bases: + if class_dict["sides"] < 3: + raise ValueError("Polygons need 3+ sides") + return type.__new__(meta, name, bases, class_dict) + +class Polygon(metaclass=ValidatePolygon): + sides = None # Must be specified by subclasses + + @classmethod + def interior_angles(cls): + return (cls.sides - 2) * 180 + +class Triangle(Polygon): + sides = 3 + +class Rectangle(Polygon): + sides = 4 + +class Nonagon(Polygon): + sides = 9 + +assert Triangle.interior_angles() == 180 +assert Rectangle.interior_angles() == 360 +assert Nonagon.interior_angles() == 1260 + + +print("Example 3") +try: + print("Before class") + + class Line(Polygon): + print("Before sides") + sides = 2 + print("After sides") + + print("After class") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 4") +class BetterPolygon: + sides = None # Must be specified by subclasses + + def __init_subclass__(cls): + super().__init_subclass__() + if cls.sides < 3: + raise ValueError("Polygons need 3+ sides") + + @classmethod + def interior_angles(cls): + return (cls.sides - 2) * 180 + +class Hexagon(BetterPolygon): + sides = 6 + +assert Hexagon.interior_angles() == 720 + + +print("Example 5") +try: + print("Before class") + + class Point(BetterPolygon): + sides = 1 + + print("After class") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 6") +class ValidateFilled(type): + def __new__(meta, name, bases, class_dict): + # Only validate subclasses of the Filled class + if bases: + if class_dict["color"] not in ("red", "green"): + raise ValueError("Fill color must be supported") + return type.__new__(meta, name, bases, class_dict) + +class Filled(metaclass=ValidateFilled): + color = None # Must be specified by subclasses + + +print("Example 7") +try: + class RedPentagon(Filled, Polygon): + color = "blue" + sides = 5 +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +class ValidatePolygon(type): + def __new__(meta, name, bases, class_dict): + # Only validate non-root classes + if not class_dict.get("is_root"): + if class_dict["sides"] < 3: + raise ValueError("Polygons need 3+ sides") + return type.__new__(meta, name, bases, class_dict) + +class Polygon(metaclass=ValidatePolygon): + is_root = True + sides = None # Must be specified by subclasses + +class ValidateFilledPolygon(ValidatePolygon): + def __new__(meta, name, bases, class_dict): + # Only validate non-root classes + if not class_dict.get("is_root"): + if class_dict["color"] not in ("red", "green"): + raise ValueError("Fill color must be supported") + return super().__new__(meta, name, bases, class_dict) + +class FilledPolygon(Polygon, metaclass=ValidateFilledPolygon): + is_root = True + color = None # Must be specified by subclasses + + +print("Example 9") +class GreenPentagon(FilledPolygon): + color = "green" + sides = 5 + +greenie = GreenPentagon() +assert isinstance(greenie, Polygon) + + +print("Example 10") +try: + class OrangePentagon(FilledPolygon): + color = "orange" + sides = 5 +except: + logging.exception('Expected') +else: + assert False + + +print("Example 11") +try: + class RedLine(FilledPolygon): + color = "red" + sides = 2 +except: + logging.exception('Expected') +else: + assert False + + +print("Example 12") +class Filled: + color = None # Must be specified by subclasses + + def __init_subclass__(cls): + super().__init_subclass__() + if cls.color not in ("red", "green", "blue"): + raise ValueError("Fills need a valid color") + + +print("Example 13") +class RedTriangle(Filled, BetterPolygon): + color = "red" + sides = 3 + +ruddy = RedTriangle() +assert isinstance(ruddy, Filled) +assert isinstance(ruddy, BetterPolygon) + + +print("Example 14") +try: + print("Before class") + + class BlueLine(Filled, BetterPolygon): + color = "blue" + sides = 2 + + print("After class") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 15") +try: + print("Before class") + + class BeigeSquare(Filled, BetterPolygon): + color = "beige" + sides = 4 + + print("After class") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 16") +class Top: + def __init_subclass__(cls): + super().__init_subclass__() + print(f"Top for {cls}") + +class Left(Top): + def __init_subclass__(cls): + super().__init_subclass__() + print(f"Left for {cls}") + +class Right(Top): + def __init_subclass__(cls): + super().__init_subclass__() + print(f"Right for {cls}") + +class Bottom(Left, Right): + def __init_subclass__(cls): + super().__init_subclass__() + print(f"Bottom for {cls}") diff --git a/example_code/item_063.py b/example_code/item_063.py new file mode 100755 index 0000000..56d0ff7 --- /dev/null +++ b/example_code/item_063.py @@ -0,0 +1,216 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import json + +class Serializable: + def __init__(self, *args): + self.args = args + + def serialize(self): + return json.dumps({"args": self.args}) + + +print("Example 2") +class Point2D(Serializable): + def __init__(self, x, y): + super().__init__(x, y) + self.x = x + self.y = y + + def __repr__(self): + return f"Point2D({self.x}, {self.y})" + +point = Point2D(5, 3) +print("Object: ", point) +print("Serialized:", point.serialize()) + + +print("Example 3") +class Deserializable(Serializable): + @classmethod + def deserialize(cls, json_data): + params = json.loads(json_data) + return cls(*params["args"]) + + +print("Example 4") +class BetterPoint2D(Deserializable): + def __init__(self, x, y): + super().__init__(x, y) + self.x = x + self.y = y + + def __repr__(self): + return f"Point2D({self.x}, {self.y})" + +before = BetterPoint2D(5, 3) +print("Before: ", before) +data = before.serialize() +print("Serialized:", data) +after = BetterPoint2D.deserialize(data) +print("After: ", after) + + +print("Example 5") +class BetterSerializable: + def __init__(self, *args): + self.args = args + + def serialize(self): + return json.dumps( + { + "class": self.__class__.__name__, + "args": self.args, + } + ) + + def __repr__(self): + name = self.__class__.__name__ + args_str = ", ".join(str(x) for x in self.args) + return f"{name}({args_str})" + + +print("Example 6") +REGISTRY = {} + +def register_class(target_class): + REGISTRY[target_class.__name__] = target_class + +def deserialize(data): + params = json.loads(data) + name = params["class"] + target_class = REGISTRY[name] + return target_class(*params["args"]) + + +print("Example 7") +class EvenBetterPoint2D(BetterSerializable): + def __init__(self, x, y): + super().__init__(x, y) + self.x = x + self.y = y + +register_class(EvenBetterPoint2D) + + +print("Example 8") +before = EvenBetterPoint2D(5, 3) +print("Before: ", before) +data = before.serialize() +print("Serialized:", data) +after = deserialize(data) +print("After: ", after) + + +print("Example 9") +class Point3D(BetterSerializable): + def __init__(self, x, y, z): + super().__init__(x, y, z) + self.x = x + self.y = y + self.z = z + +# Forgot to call register_class! Whoops! + + +print("Example 10") +try: + point = Point3D(5, 9, -4) + data = point.serialize() + deserialize(data) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 11") +class Meta(type): + def __new__(meta, name, bases, class_dict): + cls = type.__new__(meta, name, bases, class_dict) + register_class(cls) + return cls + +class RegisteredSerializable(BetterSerializable, metaclass=Meta): + pass + + +print("Example 12") +class Vector3D(RegisteredSerializable): + def __init__(self, x, y, z): + super().__init__(x, y, z) + self.x, self.y, self.z = x, y, z + +before = Vector3D(10, -7, 3) +print("Before: ", before) +data = before.serialize() +print("Serialized:", data) +print("After: ", deserialize(data)) + + +print("Example 13") +class BetterRegisteredSerializable(BetterSerializable): + def __init_subclass__(cls): + super().__init_subclass__() + register_class(cls) + +class Vector1D(BetterRegisteredSerializable): + def __init__(self, magnitude): + super().__init__(magnitude) + self.magnitude = magnitude + + +print("Example 14") +before = Vector1D(6) +print("Before: ", before) +data = before.serialize() +print("Serialized:", data) +print("After: ", deserialize(data)) diff --git a/example_code/item_064.py b/example_code/item_064.py new file mode 100755 index 0000000..5c2624e --- /dev/null +++ b/example_code/item_064.py @@ -0,0 +1,185 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class Field: + def __init__(self, column_name): + self.column_name = column_name + self.internal_name = "_" + self.column_name + + + print("Example 2") + def __get__(self, instance, instance_type): + if instance is None: + return self + return getattr(instance, self.internal_name, "") + + def __set__(self, instance, value): + setattr(instance, self.internal_name, value) + + +print("Example 3") +class Customer: + # Class attributes + first_name = Field("first_name") + last_name = Field("last_name") + prefix = Field("prefix") + suffix = Field("suffix") + + +print("Example 4") +cust = Customer() +print(f"Before: {cust.first_name!r} {cust.__dict__}") +cust.first_name = "Euclid" +print(f"After: {cust.first_name!r} {cust.__dict__}") + + +print("Example 5") +class Customer: + # Left side is redundant with right side + first_name = Field("first_name") + last_name = Field("last_name") + prefix = Field("prefix") + suffix = Field("suffix") + + +print("Example 6") +class Meta(type): + def __new__(meta, name, bases, class_dict): + for key, value in class_dict.items(): + if isinstance(value, Field): + value.column_name = key + value.internal_name = "_" + key + cls = type.__new__(meta, name, bases, class_dict) + return cls + + +print("Example 7") +class DatabaseRow(metaclass=Meta): + pass + + +print("Example 8") +class Field: + def __init__(self): + # These will be assigned by the metaclass. + self.column_name = None + self.internal_name = None + + def __get__(self, instance, instance_type): + if instance is None: + return self + return getattr(instance, self.internal_name, "") + + def __set__(self, instance, value): + setattr(instance, self.internal_name, value) + + +print("Example 9") +class BetterCustomer(DatabaseRow): + first_name = Field() + last_name = Field() + prefix = Field() + suffix = Field() + + +print("Example 10") +cust = BetterCustomer() +print(f"Before: {cust.first_name!r} {cust.__dict__}") +cust.first_name = "Euler" +print(f"After: {cust.first_name!r} {cust.__dict__}") + + +print("Example 11") +try: + class BrokenCustomer: # Missing inheritance + first_name = Field() + last_name = Field() + prefix = Field() + suffix = Field() + + cust = BrokenCustomer() + cust.first_name = "Mersenne" +except: + logging.exception('Expected') +else: + assert False + + +print("Example 12") +class Field: + def __init__(self): + self.column_name = None + self.internal_name = None + + def __set_name__(self, owner, column_name): + # Called on class creation for each descriptor + self.column_name = column_name + self.internal_name = "_" + column_name + + def __get__(self, instance, instance_type): + if instance is None: + return self + return getattr(instance, self.internal_name, "") + + def __set__(self, instance, value): + setattr(instance, self.internal_name, value) + + +print("Example 13") +class FixedCustomer: # No parent class + first_name = Field() + last_name = Field() + prefix = Field() + suffix = Field() + +cust = FixedCustomer() +print(f"Before: {cust.first_name!r} {cust.__dict__}") +cust.first_name = "Mersenne" +print(f"After: {cust.first_name!r} {cust.__dict__}") diff --git a/example_code/item_065.py b/example_code/item_065.py new file mode 100755 index 0000000..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_066.py b/example_code/item_066.py new file mode 100755 index 0000000..1d869b8 --- /dev/null +++ b/example_code/item_066.py @@ -0,0 +1,273 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +from functools import wraps + +def trace_func(func): + if hasattr(func, "tracing"): # Only decorate once + return func + + @wraps(func) + def wrapper(*args, **kwargs): + args_repr = repr(args) + kwargs_repr = repr(kwargs) + result = None + try: + result = func(*args, **kwargs) + return result + except Exception as e: + result = e + raise + finally: + print( + f"{func.__name__}" + f"({args_repr}, {kwargs_repr}) -> " + f"{result!r}" + ) + + wrapper.tracing = True + return wrapper + + +print("Example 2") +class TraceDict(dict): + @trace_func + def __init__(self, *args, **kwargs): + return super().__init__(*args, **kwargs) + + @trace_func + def __setitem__(self, *args, **kwargs): + return super().__setitem__(*args, **kwargs) + + @trace_func + def __getitem__(self, *args, **kwargs): + return super().__getitem__(*args, **kwargs) + + +print("Example 3") +trace_dict = TraceDict([("hi", 1)]) +trace_dict["there"] = 2 +trace_dict["hi"] +try: + trace_dict["does not exist"] +except KeyError: + pass # Expected +else: + assert False + + +print("Example 4") +import types + +TRACE_TYPES = ( + types.MethodType, + types.FunctionType, + types.BuiltinFunctionType, + types.BuiltinMethodType, + types.MethodDescriptorType, + types.ClassMethodDescriptorType, + types.WrapperDescriptorType, +) + +IGNORE_METHODS = ( + "__repr__", + "__str__", +) + +class TraceMeta(type): + def __new__(meta, name, bases, class_dict): + klass = super().__new__(meta, name, bases, class_dict) + + for key in dir(klass): + if key in IGNORE_METHODS: + continue + + value = getattr(klass, key) + if not isinstance(value, TRACE_TYPES): + continue + + wrapped = trace_func(value) + setattr(klass, key, wrapped) + + return klass + + +print("Example 5") +class TraceDict(dict, metaclass=TraceMeta): + pass + +trace_dict = TraceDict([("hi", 1)]) +trace_dict["there"] = 2 +trace_dict["hi"] +try: + trace_dict["does not exist"] +except KeyError: + pass # Expected +else: + assert False + + +print("Example 6") +try: + class OtherMeta(type): + pass + + class SimpleDict(dict, metaclass=OtherMeta): + pass + + class ChildTraceDict(SimpleDict, metaclass=TraceMeta): + pass +except: + logging.exception('Expected') +else: + assert False + + +print("Example 7") +class TraceMeta(type): + def __new__(meta, name, bases, class_dict): + klass = super().__new__(meta, name, bases, class_dict) + + for key in dir(klass): + if key in IGNORE_METHODS: + continue + + value = getattr(klass, key) + if not isinstance(value, TRACE_TYPES): + continue + + wrapped = trace_func(value) + setattr(klass, key, wrapped) + + return klass + + +class OtherMeta(TraceMeta): + pass + +class SimpleDict(dict, metaclass=OtherMeta): + pass + +class ChildTraceDict(SimpleDict, metaclass=TraceMeta): + pass + +trace_dict = ChildTraceDict([("hi", 1)]) +trace_dict["there"] = 2 +trace_dict["hi"] +try: + trace_dict["does not exist"] +except KeyError: + pass # Expected +else: + assert False + + +print("Example 8") +def my_class_decorator(klass): + klass.extra_param = "hello" + return klass + +@my_class_decorator +class MyClass: + pass + +print(MyClass) +print(MyClass.extra_param) + + +print("Example 9") +def trace(klass): + for key in dir(klass): + if key in IGNORE_METHODS: + continue + + value = getattr(klass, key) + if not isinstance(value, TRACE_TYPES): + continue + + wrapped = trace_func(value) + setattr(klass, key, wrapped) + + return klass + + +print("Example 10") +@trace +class DecoratedTraceDict(dict): + pass + +trace_dict = DecoratedTraceDict([("hi", 1)]) +trace_dict["there"] = 2 +trace_dict["hi"] +try: + trace_dict["does not exist"] +except KeyError: + pass # Expected +else: + assert False + + +print("Example 11") +class OtherMeta(type): + pass + +@trace +class HasMetaTraceDict(dict, metaclass=OtherMeta): + pass + +trace_dict = HasMetaTraceDict([("hi", 1)]) +trace_dict["there"] = 2 +trace_dict["hi"] +try: + trace_dict["does not exist"] +except KeyError: + pass # Expected +else: + assert False diff --git a/example_code/item_067.py b/example_code/item_067.py new file mode 100755 index 0000000..73b94d4 --- /dev/null +++ b/example_code/item_067.py @@ -0,0 +1,188 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import subprocess + +# Enable these lines to make this example work on Windows +# import os +# os.environ['COMSPEC'] = 'powershell' +result = subprocess.run( + ["echo", "Hello from the child!"], + capture_output=True, + # Enable this line to make this example work on Windows + # shell=True, + encoding="utf-8", +) + +result.check_returncode() # No exception means it exited cleanly +print(result.stdout) + + +print("Example 2") +# Use this line instead to make this example work on Windows +# proc = subprocess.Popen(['sleep', '1'], shell=True) +proc = subprocess.Popen(["sleep", "1"]) +while proc.poll() is None: + print("Working...") + # Some time-consuming work here + import time + + time.sleep(0.3) + +print("Exit status", proc.poll()) + + +print("Example 3") +import time + +start = time.perf_counter() +sleep_procs = [] +for _ in range(10): + # Use this line instead to make this example work on Windows + # proc = subprocess.Popen(['sleep', '1'], shell=True) + proc = subprocess.Popen(["sleep", "1"]) + sleep_procs.append(proc) + + +print("Example 4") +for proc in sleep_procs: + proc.communicate() + +end = time.perf_counter() +delta = end - start +print(f"Finished in {delta:.3} seconds") + + +print("Example 5") +import os + +# On Windows, after installing OpenSSL, you may need to +# alias it in your PowerShell path with a command like: +# $env:path = $env:path + ";C:\Program Files\OpenSSL-Win64\bin" + +def run_encrypt(data): + env = os.environ.copy() + env["password"] = "zf7ShyBhZOraQDdE/FiZpm/m/8f9X+M1" + proc = subprocess.Popen( + ["openssl", "enc", "-des3", "-pbkdf2", "-pass", "env:password"], + env=env, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + proc.stdin.write(data) + proc.stdin.flush() # Ensure that the child gets input + return proc + + +print("Example 6") +procs = [] +for _ in range(3): + data = os.urandom(10) + proc = run_encrypt(data) + procs.append(proc) + + +print("Example 7") +for proc in procs: + out, _ = proc.communicate() + print(out[-10:]) + + +print("Example 8") +def run_hash(input_stdin): + return subprocess.Popen( + ["openssl", "dgst", "-sha256", "-binary"], + stdin=input_stdin, + stdout=subprocess.PIPE, + ) + + +print("Example 9") +encrypt_procs = [] +hash_procs = [] +for _ in range(3): + data = os.urandom(100) + + encrypt_proc = run_encrypt(data) + encrypt_procs.append(encrypt_proc) + + hash_proc = run_hash(encrypt_proc.stdout) + hash_procs.append(hash_proc) + + # Ensure that the child consumes the input stream and + # the communicate() method doesn't inadvertently steal + # input from the child. Also lets SIGPIPE propagate to + # the upstream process if the downstream process dies. + encrypt_proc.stdout.close() + encrypt_proc.stdout = None + + +print("Example 10") +for proc in encrypt_procs: + proc.communicate() + assert proc.returncode == 0 + +for proc in hash_procs: + out, _ = proc.communicate() + print(out[-10:]) + assert proc.returncode == 0 + + +print("Example 11") +# Use this line instead to make this example work on Windows +# proc = subprocess.Popen(['sleep', '10'], shell=True) +proc = subprocess.Popen(["sleep", "10"]) +try: + proc.communicate(timeout=0.1) +except subprocess.TimeoutExpired: + proc.terminate() + proc.wait() + +print("Exit status", proc.poll()) diff --git a/example_code/item_37.py b/example_code/item_068.py similarity index 50% rename from example_code/item_37.py rename to example_code/item_068.py index 57d1286..77d3641 100755 --- a/example_code/item_37.py +++ b/example_code/item_068.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,30 +14,64 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment +### 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() -# Example 1 +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") def factorize(number): for i in range(1, number + 1): if number % i == 0: yield i -# Example 2 -from time import time -numbers = [2139079, 1214759, 1516637, 1852285] -start = time() +print("Example 2") +import time + +numbers = [7775876, 6694411, 5038540, 5426782, + 9934740, 9168996, 5271226, 8288002, + 9403196, 6678888, 6776096, 9582542, + 7107467, 9633726, 5747908, 7613918] +start = time.perf_counter() + for number in numbers: list(factorize(number)) -end = time() -print('Took %.3f seconds' % (end - start)) +end = time.perf_counter() +delta = end - start +print(f"Took {delta:.3f} seconds") -# Example 3 + +print("Example 3") from threading import Thread class FactorizeThread(Thread): @@ -49,8 +83,9 @@ def run(self): self.factors = list(factorize(self.number)) -# Example 4 -start = time() +print("Example 4") +start = time.perf_counter() + threads = [] for number in numbers: thread = FactorizeThread(number) @@ -58,32 +93,37 @@ def run(self): threads.append(thread) -# Example 5 +print("Example 5") for thread in threads: thread.join() -end = time() -print('Took %.3f seconds' % (end - start)) +end = time.perf_counter() +delta = end - start +print(f"Took {delta:.3f} seconds") -# Example 6 -import select, socket -# Creating the socket is specifically to support Windows. Windows can't do -# a select call with an empty list. +print("Example 6") +import select +import socket + def slow_systemcall(): select.select([socket.socket()], [], [], 0.1) -# Example 7 -start = time() +print("Example 7") +start = time.perf_counter() + for _ in range(5): slow_systemcall() -end = time() -print('Took %.3f seconds' % (end - start)) +end = time.perf_counter() +delta = end - start +print(f"Took {delta:.3f} seconds") + + +print("Example 8") +start = time.perf_counter() -# Example 8 -start = time() threads = [] for _ in range(5): thread = Thread(target=slow_systemcall) @@ -91,13 +131,16 @@ def slow_systemcall(): threads.append(thread) -# Example 9 +print("Example 9") def compute_helicopter_location(index): pass for i in range(5): compute_helicopter_location(i) + for thread in threads: thread.join() -end = time() -print('Took %.3f seconds' % (end - start)) + +end = time.perf_counter() +delta = end - start +print(f"Took {delta:.3f} seconds") diff --git a/example_code/item_069.py b/example_code/item_069.py new file mode 100755 index 0000000..b98fab1 --- /dev/null +++ b/example_code/item_069.py @@ -0,0 +1,156 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +counter = 0 + +def read_sensor(sensor_index): + # Returns sensor data or raises an exception + # Nothing actually happens here, but this is where + # the blocking I/O would go. + pass + +def get_offset(data): + # Always returns 1 or greater + return 1 + +def worker(sensor_index, how_many): + global counter + # I have a barrier in here so the workers synchronize + # when they start counting, otherwise it's hard to get a race + # because the overhead of starting a thread is high. + BARRIER.wait() + for _ in range(how_many): + data = read_sensor(sensor_index) + # Note that the value passed to += must be a function call or other + # non-trivial expression in order to cause the CPython eval loop to + # check whether it should release the GIL. This is a side-effect of + # an optimization. See https://github.com/python/cpython/commit/4958f5d69dd2bf86866c43491caf72f774ddec97 for details. + counter += get_offset(data) + + +print("Example 2") +from threading import Thread + +how_many = 10**6 +sensor_count = 4 + +from threading import Barrier + +BARRIER = Barrier(sensor_count) + +threads = [] +for i in range(sensor_count): + thread = Thread(target=worker, args=(i, how_many)) + threads.append(thread) + thread.start() + +for thread in threads: + thread.join() + +expected = how_many * sensor_count +print(f"Counter should be {expected}, got {counter}") + + +print("Example 3") +data = None +counter += get_offset(data) + + +print("Example 4") +value = counter +delta = get_offset(data) +result = value + delta +counter = result + + +print("Example 5") +data_a = None +data_b = None +# Running in Thread A +value_a = counter +delta_a = get_offset(data_a) +# Context switch to Thread B +value_b = counter +delta_b = get_offset(data_b) +result_b = value_b + delta_b +counter = result_b +# Context switch back to Thread A +result_a = value_a + delta_a +counter = result_a + + +print("Example 6") +from threading import Lock + +counter = 0 +counter_lock = Lock() + +def locking_worker(sensor_index, how_many): + global counter + BARRIER.wait() + for _ in range(how_many): + data = read_sensor(sensor_index) + with counter_lock: # Added + counter += get_offset(data) + + +print("Example 7") +BARRIER = Barrier(sensor_count) + +for i in range(sensor_count): + thread = Thread(target=locking_worker, args=(i, how_many)) + threads.append(thread) + thread.start() + +for thread in threads: + thread.join() + +expected = how_many * sensor_count +print(f"Counter should be {expected}, got {counter}") diff --git a/example_code/item_07.py b/example_code/item_07.py deleted file mode 100755 index c90a497..0000000 --- a/example_code/item_07.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -squares = [x**2 for x in a] -print(squares) - - -# Example 2 -squares = map(lambda x: x ** 2, a) -print(list(squares)) - - -# Example 3 -even_squares = [x**2 for x in a if x % 2 == 0] -print(even_squares) - - -# Example 4 -alt = map(lambda x: x**2, filter(lambda x: x % 2 == 0, a)) -assert even_squares == list(alt) - - -# Example 5 -chile_ranks = {'ghost': 1, 'habanero': 2, 'cayenne': 3} -rank_dict = {rank: name for name, rank in chile_ranks.items()} -chile_len_set = {len(name) for name in rank_dict.values()} -print(rank_dict) -print(chile_len_set) diff --git a/example_code/item_070.py b/example_code/item_070.py new file mode 100755 index 0000000..351a99f --- /dev/null +++ b/example_code/item_070.py @@ -0,0 +1,382 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def download(item): + return item + +def resize(item): + return item + +def upload(item): + return item + + +print("Example 2") +from collections import deque +from threading import Lock + +class MyQueue: + def __init__(self): + self.items = deque() + self.lock = Lock() + + + print("Example 3") + def put(self, item): + with self.lock: + self.items.append(item) + + + print("Example 4") + def get(self): + with self.lock: + return self.items.popleft() + + +print("Example 5") +from threading import Thread +import time + +class Worker(Thread): + def __init__(self, func, in_queue, out_queue): + super().__init__() + self.func = func + self.in_queue = in_queue + self.out_queue = out_queue + self.polled_count = 0 + self.work_done = 0 + + + print("Example 6") + def run(self): + while True: + self.polled_count += 1 + try: + item = self.in_queue.get() + except IndexError: + time.sleep(0.01) # No work to do + except AttributeError: + # The magic exit signal to make this easy to show in + # example code, but don't use this in practice. + return + else: + result = self.func(item) + self.out_queue.put(result) + self.work_done += 1 + + +print("Example 7") +download_queue = MyQueue() +resize_queue = MyQueue() +upload_queue = MyQueue() +done_queue = MyQueue() +threads = [ + Worker(download, download_queue, resize_queue), + Worker(resize, resize_queue, upload_queue), + Worker(upload, upload_queue, done_queue), +] + + +print("Example 8") +for thread in threads: + thread.start() + +for _ in range(1000): + download_queue.put(object()) + + +print("Example 9") +while len(done_queue.items) < 1000: + # Do something useful while waiting + time.sleep(0.1) +# Stop all the threads by causing an exception in their +# run methods. +for thread in threads: + thread.in_queue = None + thread.join() + + +print("Example 10") +processed = len(done_queue.items) +polled = sum(t.polled_count for t in threads) +print(f"Processed {processed} items after " f"polling {polled} times") + + +print("Example 11") +from queue import Queue + +my_queue = Queue() + +def consumer(): + print("Consumer waiting") + my_queue.get() # Runs after put() below + print("Consumer done") + +thread = Thread(target=consumer) +thread.start() + + +print("Example 12") +print("Producer putting") +my_queue.put(object()) # Runs before get() above +print("Producer done") +thread.join() + + +print("Example 13") +my_queue = Queue(1) # Buffer size of 1 + +def consumer(): + time.sleep(0.1) # Wait + my_queue.get() # Runs second + print("Consumer got 1") + my_queue.get() # Runs fourth + print("Consumer got 2") + print("Consumer done") + +thread = Thread(target=consumer) +thread.start() + + +print("Example 14") +my_queue.put(object()) # Runs first +print("Producer put 1") +my_queue.put(object()) # Runs third +print("Producer put 2") +print("Producer done") +thread.join() + + +print("Example 15") +in_queue = Queue() + +def consumer(): + print("Consumer waiting") + work = in_queue.get() # Runs second + print("Consumer working") + # Doing work + print("Consumer done") + in_queue.task_done() # Runs third + +thread = Thread(target=consumer) +thread.start() + + +print("Example 16") +print("Producer putting") +in_queue.put(object()) # Runs first +print("Producer waiting") +in_queue.join() # Runs fourth +print("Producer done") +thread.join() + + +print("Example 17") +from queue import ShutDown + +my_queue2 = Queue() + +def consumer(): + while True: + try: + item = my_queue2.get() + except ShutDown: + print("Terminating!") + return + else: + print("Got item", item) + my_queue2.task_done() + +thread = Thread(target=consumer) +my_queue2.put(1) +my_queue2.put(2) +my_queue2.put(3) +my_queue2.shutdown() + +thread.start() + +my_queue2.join() +thread.join() +print("Done") + + +print("Example 18") +class StoppableWorker(Thread): + def __init__(self, func, in_queue, out_queue): + super().__init__() + self.func = func + self.in_queue = in_queue + self.out_queue = out_queue + + def run(self): + while True: + try: + item = self.in_queue.get() + except ShutDown: + return + else: + result = self.func(item) + self.out_queue.put(result) + self.in_queue.task_done() + + +print("Example 19") +download_queue = Queue() +resize_queue = Queue(100) +upload_queue = Queue(100) +done_queue = Queue() + +threads = [ + StoppableWorker(download, download_queue, resize_queue), + StoppableWorker(resize, resize_queue, upload_queue), + StoppableWorker(upload, upload_queue, done_queue), +] + +for thread in threads: + thread.start() + + +print("Example 20") +for _ in range(1000): + download_queue.put(object()) + + +print("Example 21") +download_queue.shutdown() +download_queue.join() + +resize_queue.shutdown() +resize_queue.join() + +upload_queue.shutdown() +upload_queue.join() + + +print("Example 22") +done_queue.shutdown() + +counter = 0 + +while True: + try: + item = done_queue.get() + except ShutDown: + break + else: + # Process the item + done_queue.task_done() + counter += 1 + +done_queue.join() + +for thread in threads: + thread.join() + +print(counter, "items finished") + + +print("Example 23") +def start_threads(count, *args): + threads = [StoppableWorker(*args) for _ in range(count)] + for thread in threads: + thread.start() + return threads + +def drain_queue(input_queue): + input_queue.shutdown() + + counter = 0 + + while True: + try: + item = input_queue.get() + except ShutDown: + break + else: + input_queue.task_done() + counter += 1 + + input_queue.join() + + return counter + + +print("Example 24") +download_queue = Queue() +resize_queue = Queue(100) +upload_queue = Queue(100) +done_queue = Queue() + +threads = ( + start_threads(3, download, download_queue, resize_queue) + + start_threads(4, resize, resize_queue, upload_queue) + + start_threads(5, upload, upload_queue, done_queue) +) + + +print("Example 25") +for _ in range(2000): + download_queue.put(object()) + +download_queue.shutdown() +download_queue.join() + +resize_queue.shutdown() +resize_queue.join() + +upload_queue.shutdown() +upload_queue.join() + +counter = drain_queue(done_queue) + +for thread in threads: + thread.join() + +print(counter, "items finished") diff --git a/example_code/item_071.py b/example_code/item_071.py new file mode 100755 index 0000000..6ff868d --- /dev/null +++ b/example_code/item_071.py @@ -0,0 +1,237 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +ALIVE = "*" +EMPTY = "-" + + +print("Example 2") +class Grid: + def __init__(self, height, width): + self.height = height + self.width = width + self.rows = [] + for _ in range(self.height): + self.rows.append([EMPTY] * self.width) + + def get(self, y, x): + return self.rows[y % self.height][x % self.width] + + def set(self, y, x, state): + self.rows[y % self.height][x % self.width] = state + + def __str__(self): + output = "" + for row in self.rows: + for cell in row: + output += cell + output += "\n" + return output + + +print("Example 3") +grid = Grid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) +print(grid) + + +print("Example 4") +def count_neighbors(y, x, get_cell): + n_ = get_cell(y - 1, x + 0) # North + ne = get_cell(y - 1, x + 1) # Northeast + e_ = get_cell(y + 0, x + 1) # East + se = get_cell(y + 1, x + 1) # Southeast + s_ = get_cell(y + 1, x + 0) # South + sw = get_cell(y + 1, x - 1) # Southwest + w_ = get_cell(y + 0, x - 1) # West + nw = get_cell(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + + +alive = {(9, 5), (9, 6)} +seen = set() + +def fake_get(y, x): + position = (y, x) + seen.add(position) + return ALIVE if position in alive else EMPTY + +count = count_neighbors(10, 5, fake_get) +assert count == 2 + +expected_seen = { + (9, 5), + (9, 6), + (10, 6), + (11, 6), + (11, 5), + (11, 4), + (10, 4), + (9, 4), +} +assert seen == expected_seen + + +print("Example 5") +def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY # Die: Too few + elif neighbors > 3: + return EMPTY # Die: Too many + else: + if neighbors == 3: + return ALIVE # Regenerate + return state + +assert game_logic(ALIVE, 0) == EMPTY +assert game_logic(ALIVE, 1) == EMPTY +assert game_logic(ALIVE, 2) == ALIVE +assert game_logic(ALIVE, 3) == ALIVE +assert game_logic(ALIVE, 4) == EMPTY +assert game_logic(EMPTY, 0) == EMPTY +assert game_logic(EMPTY, 1) == EMPTY +assert game_logic(EMPTY, 2) == EMPTY +assert game_logic(EMPTY, 3) == ALIVE +assert game_logic(EMPTY, 4) == EMPTY + + +print("Example 6") +def step_cell(y, x, get_cell, set_cell): + state = get_cell(y, x) + neighbors = count_neighbors(y, x, get_cell) + next_state = game_logic(state, neighbors) + set_cell(y, x, next_state) + + +alive = {(10, 5), (9, 5), (9, 6)} +new_state = None + +def fake_get(y, x): + return ALIVE if (y, x) in alive else EMPTY + +def fake_set(y, x, state): + global new_state + new_state = state + +# Stay alive +step_cell(10, 5, fake_get, fake_set) +assert new_state == ALIVE + +# Stay dead +alive.remove((10, 5)) +step_cell(10, 5, fake_get, fake_set) +assert new_state == EMPTY + +# Regenerate +alive.add((10, 6)) +step_cell(10, 5, fake_get, fake_set) +assert new_state == ALIVE + + +print("Example 7") +def simulate(grid): + next_grid = Grid(grid.height, grid.width) + for y in range(grid.height): + for x in range(grid.width): + step_cell(y, x, grid.get, next_grid.set) + return next_grid + + +print("Example 8") +class ColumnPrinter: + def __init__(self): + self.columns = [] + + def append(self, data): + self.columns.append(data) + + def __str__(self): + row_count = 1 + for data in self.columns: + row_count = max(row_count, len(data.splitlines()) + 1) + + rows = [""] * row_count + for j in range(row_count): + for i, data in enumerate(self.columns): + line = data.splitlines()[max(0, j - 1)] + if j == 0: + padding = " " * (len(line) // 2) + rows[j] += padding + str(i) + padding + else: + rows[j] += line + + if (i + 1) < len(self.columns): + rows[j] += " | " + + return "\n".join(rows) + + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = simulate(grid) + +print(columns) + + +print("Example 9") +def game_logic(state, neighbors): + # Do some blocking input/output in here: + data = my_socket.recv(100) diff --git a/example_code/item_072.py b/example_code/item_072.py new file mode 100755 index 0000000..a0e57bb --- /dev/null +++ b/example_code/item_072.py @@ -0,0 +1,216 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +from threading import Lock + +ALIVE = "*" +EMPTY = "-" + +class Grid: + def __init__(self, height, width): + self.height = height + self.width = width + self.rows = [] + for _ in range(self.height): + self.rows.append([EMPTY] * self.width) + + def get(self, y, x): + return self.rows[y % self.height][x % self.width] + + def set(self, y, x, state): + self.rows[y % self.height][x % self.width] = state + + def __str__(self): + output = "" + for row in self.rows: + for cell in row: + output += cell + output += "\n" + return output + + +class LockingGrid(Grid): + def __init__(self, height, width): + super().__init__(height, width) + self.lock = Lock() + + def __str__(self): + with self.lock: + return super().__str__() + + def get(self, y, x): + with self.lock: + return super().get(y, x) + + def set(self, y, x, state): + with self.lock: + return super().set(y, x, state) + + +print("Example 2") +from threading import Thread + +def count_neighbors(y, x, get_cell): + n_ = get_cell(y - 1, x + 0) # North + ne = get_cell(y - 1, x + 1) # Northeast + e_ = get_cell(y + 0, x + 1) # East + se = get_cell(y + 1, x + 1) # Southeast + s_ = get_cell(y + 1, x + 0) # South + sw = get_cell(y + 1, x - 1) # Southwest + w_ = get_cell(y + 0, x - 1) # West + nw = get_cell(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +def game_logic(state, neighbors): + # This version of the function is just to illustrate the point + # that I/O is possible, but for example code we'll simply run + # the normal game logic (below) so it's easier to understand. + # Do some blocking input/output in here: + data = my_socket.recv(100) + +def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY + elif neighbors > 3: + return EMPTY + else: + if neighbors == 3: + return ALIVE + return state + +def step_cell(y, x, get_cell, set_cell): + state = get_cell(y, x) + neighbors = count_neighbors(y, x, get_cell) + next_state = game_logic(state, neighbors) + set_cell(y, x, next_state) + +def simulate_threaded(grid): + next_grid = LockingGrid(grid.height, grid.width) + + threads = [] + for y in range(grid.height): + for x in range(grid.width): + args = (y, x, grid.get, next_grid.set) + thread = Thread(target=step_cell, args=args) + thread.start() # Fan-out + threads.append(thread) + + for thread in threads: + thread.join() # Fan-in + + return next_grid + + +print("Example 3") +class ColumnPrinter: + def __init__(self): + self.columns = [] + + def append(self, data): + self.columns.append(data) + + def __str__(self): + row_count = 1 + for data in self.columns: + row_count = max(row_count, len(data.splitlines()) + 1) + + rows = [""] * row_count + for j in range(row_count): + for i, data in enumerate(self.columns): + line = data.splitlines()[max(0, j - 1)] + if j == 0: + padding = " " * (len(line) // 2) + rows[j] += padding + str(i) + padding + else: + rows[j] += line + + if (i + 1) < len(self.columns): + rows[j] += " | " + + return "\n".join(rows) + + +grid = LockingGrid(5, 9) # Changed +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = simulate_threaded(grid) # Changed + +print(columns) + + +print("Example 4") +def game_logic(state, neighbors): + raise OSError("Problem with I/O") + + +print("Example 5") +import contextlib +import io + +fake_stderr = io.StringIO() +with contextlib.redirect_stderr(fake_stderr): + thread = Thread(target=game_logic, args=(ALIVE, 3)) + thread.start() + thread.join() + +print(fake_stderr.getvalue()) diff --git a/example_code/item_073.py b/example_code/item_073.py new file mode 100755 index 0000000..8e23346 --- /dev/null +++ b/example_code/item_073.py @@ -0,0 +1,413 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +from queue import Queue + +in_queue = Queue() +out_queue = Queue() + + +print("Example 2") +from threading import Thread + +from queue import ShutDown + + +class StoppableWorker(Thread): + def __init__(self, func, in_queue, out_queue, *args, **kwargs): + super().__init__(*args, **kwargs) + self.func = func + self.in_queue = in_queue + self.out_queue = out_queue + + def run(self): + while True: + try: + item = self.in_queue.get() + except ShutDown: + return + else: + result = self.func(item) + self.out_queue.put(result) + self.in_queue.task_done() + + +def game_logic(state, neighbors): + # Do some blocking input/output in here: + data = my_socket.recv(100) + +def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY + elif neighbors > 3: + return EMPTY + else: + if neighbors == 3: + return ALIVE + return state + +def game_logic_thread(item): + y, x, state, neighbors = item + try: + next_state = game_logic(state, neighbors) + except Exception as e: + next_state = e + return (y, x, next_state) + +# Start the threads upfront +threads = [] +for _ in range(5): + thread = StoppableWorker(game_logic_thread, in_queue, out_queue) + thread.start() + threads.append(thread) + + +print("Example 3") +ALIVE = "*" +EMPTY = "-" + +class SimulationError(Exception): + pass + +class Grid: + def __init__(self, height, width): + self.height = height + self.width = width + self.rows = [] + for _ in range(self.height): + self.rows.append([EMPTY] * self.width) + + def get(self, y, x): + return self.rows[y % self.height][x % self.width] + + def set(self, y, x, state): + self.rows[y % self.height][x % self.width] = state + + def __str__(self): + output = "" + for row in self.rows: + for cell in row: + output += cell + output += "\n" + return output + + +def count_neighbors(y, x, get_cell): + n_ = get_cell(y - 1, x + 0) # North + ne = get_cell(y - 1, x + 1) # Northeast + e_ = get_cell(y + 0, x + 1) # East + se = get_cell(y + 1, x + 1) # Southeast + s_ = get_cell(y + 1, x + 0) # South + sw = get_cell(y + 1, x - 1) # Southwest + w_ = get_cell(y + 0, x - 1) # West + nw = get_cell(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +def simulate_pipeline(grid, in_queue, out_queue): + for y in range(grid.height): + for x in range(grid.width): + state = grid.get(y, x) + neighbors = count_neighbors(y, x, grid.get) + in_queue.put((y, x, state, neighbors)) # Fan-out + + in_queue.join() + item_count = out_queue.qsize() + + next_grid = Grid(grid.height, grid.width) + for _ in range(item_count): + item = out_queue.get() # Fan-in + y, x, next_state = item + if isinstance(next_state, Exception): + raise SimulationError(y, x) from next_state + next_grid.set(y, x, next_state) + + return next_grid + + +print("Example 4") +try: + def game_logic(state, neighbors): + raise OSError("Problem with I/O in game_logic") + + simulate_pipeline(Grid(1, 1), in_queue, out_queue) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +# Restore the working version of this function +def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY + elif neighbors > 3: + return EMPTY + else: + if neighbors == 3: + return ALIVE + return state + +class ColumnPrinter: + def __init__(self): + self.columns = [] + + def append(self, data): + self.columns.append(data) + + def __str__(self): + row_count = 1 + for data in self.columns: + row_count = max(row_count, len(data.splitlines()) + 1) + + rows = [""] * row_count + for j in range(row_count): + for i, data in enumerate(self.columns): + line = data.splitlines()[max(0, j - 1)] + if j == 0: + padding = " " * (len(line) // 2) + rows[j] += padding + str(i) + padding + else: + rows[j] += line + + if (i + 1) < len(self.columns): + rows[j] += " | " + + return "\n".join(rows) + + +grid = Grid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = simulate_pipeline(grid, in_queue, out_queue) + +print(columns) + +in_queue.shutdown() +in_queue.join() + +for thread in threads: + thread.join() + + +print("Example 6") +def count_neighbors(y, x, get_cell): + # Do some blocking input/output in here: + data = my_socket.recv(100) + + +print("Example 7") +def count_neighbors(y, x, get_cell): + n_ = get_cell(y - 1, x + 0) # North + ne = get_cell(y - 1, x + 1) # Northeast + e_ = get_cell(y + 0, x + 1) # East + se = get_cell(y + 1, x + 1) # Southeast + s_ = get_cell(y + 1, x + 0) # South + sw = get_cell(y + 1, x - 1) # Southwest + w_ = get_cell(y + 0, x - 1) # West + nw = get_cell(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +def count_neighbors_thread(item): + y, x, state, get_cell = item + try: + neighbors = count_neighbors(y, x, get_cell) + except Exception as e: + neighbors = e + return (y, x, state, neighbors) + +def game_logic_thread(item): + y, x, state, neighbors = item + if isinstance(neighbors, Exception): + next_state = neighbors + else: + try: + next_state = game_logic(state, neighbors) + except Exception as e: + next_state = e + return (y, x, next_state) + +from threading import Lock + +class LockingGrid(Grid): + def __init__(self, height, width): + super().__init__(height, width) + self.lock = Lock() + + def __str__(self): + with self.lock: + return super().__str__() + + def get(self, y, x): + with self.lock: + return super().get(y, x) + + def set(self, y, x, state): + with self.lock: + return super().set(y, x, state) + + +print("Example 8") +in_queue = Queue() +logic_queue = Queue() +out_queue = Queue() + +threads = [] + +for _ in range(5): + thread = StoppableWorker( + count_neighbors_thread, in_queue, logic_queue + ) + thread.start() + threads.append(thread) + +for _ in range(5): + thread = StoppableWorker( + game_logic_thread, logic_queue, out_queue + ) + thread.start() + threads.append(thread) + + +print("Example 9") +def simulate_phased_pipeline(grid, in_queue, logic_queue, out_queue): + for y in range(grid.height): + for x in range(grid.width): + state = grid.get(y, x) + item = (y, x, state, grid.get) + in_queue.put(item) # Fan-out + + in_queue.join() + logic_queue.join() # Pipeline sequencing + item_count = out_queue.qsize() + + next_grid = LockingGrid(grid.height, grid.width) + for _ in range(item_count): + y, x, next_state = out_queue.get() # Fan-in + if isinstance(next_state, Exception): + raise SimulationError(y, x) from next_state + next_grid.set(y, x, next_state) + + return next_grid + + +print("Example 10") +grid = LockingGrid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = simulate_phased_pipeline( + grid, in_queue, logic_queue, out_queue + ) + +print(columns) + +in_queue.shutdown() +in_queue.join() + +logic_queue.shutdown() +logic_queue.join() + +for thread in threads: + thread.join() + + +print("Example 11") +# Make sure exception propagation works as expected +def count_neighbors(*args): + raise OSError("Problem with I/O in count_neighbors") + +in_queue = Queue() +logic_queue = Queue() +out_queue = Queue() + +threads = [ + StoppableWorker( + count_neighbors_thread, in_queue, logic_queue, daemon=True + ), + StoppableWorker( + game_logic_thread, logic_queue, out_queue, daemon=True + ), +] + +for thread in threads: + thread.start() + +try: + simulate_phased_pipeline(grid, in_queue, logic_queue, out_queue) +except SimulationError: + pass # Expected +else: + assert False diff --git a/example_code/item_074.py b/example_code/item_074.py new file mode 100755 index 0000000..ec48443 --- /dev/null +++ b/example_code/item_074.py @@ -0,0 +1,210 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +ALIVE = "*" +EMPTY = "-" + +class Grid: + def __init__(self, height, width): + self.height = height + self.width = width + self.rows = [] + for _ in range(self.height): + self.rows.append([EMPTY] * self.width) + + def get(self, y, x): + return self.rows[y % self.height][x % self.width] + + def set(self, y, x, state): + self.rows[y % self.height][x % self.width] = state + + def __str__(self): + output = "" + for row in self.rows: + for cell in row: + output += cell + output += "\n" + return output + + +from threading import Lock + +class LockingGrid(Grid): + def __init__(self, height, width): + super().__init__(height, width) + self.lock = Lock() + + def __str__(self): + with self.lock: + return super().__str__() + + def get(self, y, x): + with self.lock: + return super().get(y, x) + + def set(self, y, x, state): + with self.lock: + return super().set(y, x, state) + + +def count_neighbors(y, x, get_cell): + n_ = get_cell(y - 1, x + 0) # North + ne = get_cell(y - 1, x + 1) # Northeast + e_ = get_cell(y + 0, x + 1) # East + se = get_cell(y + 1, x + 1) # Southeast + s_ = get_cell(y + 1, x + 0) # South + sw = get_cell(y + 1, x - 1) # Southwest + w_ = get_cell(y + 0, x - 1) # West + nw = get_cell(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +def game_logic(state, neighbors): + # Do some blocking input/output in here: + data = my_socket.recv(100) + +def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY + elif neighbors > 3: + return EMPTY + else: + if neighbors == 3: + return ALIVE + return state + +def step_cell(y, x, get_cell, set_cell): + state = get_cell(y, x) + neighbors = count_neighbors(y, x, get_cell) + next_state = game_logic(state, neighbors) + set_cell(y, x, next_state) + + +print("Example 2") +from concurrent.futures import ThreadPoolExecutor + +def simulate_pool(pool, grid): + next_grid = LockingGrid(grid.height, grid.width) + + futures = [] + for y in range(grid.height): + for x in range(grid.width): + args = (y, x, grid.get, next_grid.set) + future = pool.submit(step_cell, *args) # Fan-out + futures.append(future) + + for future in futures: + future.result() # Fan-in + + return next_grid + + +print("Example 3") +class ColumnPrinter: + def __init__(self): + self.columns = [] + + def append(self, data): + self.columns.append(data) + + def __str__(self): + row_count = 1 + for data in self.columns: + row_count = max(row_count, len(data.splitlines()) + 1) + + rows = [""] * row_count + for j in range(row_count): + for i, data in enumerate(self.columns): + line = data.splitlines()[max(0, j - 1)] + if j == 0: + padding = " " * (len(line) // 2) + rows[j] += padding + str(i) + padding + else: + rows[j] += line + + if (i + 1) < len(self.columns): + rows[j] += " | " + + return "\n".join(rows) + + +grid = LockingGrid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +with ThreadPoolExecutor(max_workers=10) as pool: + for i in range(5): + columns.append(str(grid)) + grid = simulate_pool(pool, grid) + +print(columns) + + +print("Example 4") +try: + def game_logic(state, neighbors): + raise OSError("Problem with I/O") + + with ThreadPoolExecutor(max_workers=10) as pool: + task = pool.submit(game_logic, ALIVE, 3) + task.result() +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_075.py b/example_code/item_075.py new file mode 100755 index 0000000..f92fbb6 --- /dev/null +++ b/example_code/item_075.py @@ -0,0 +1,233 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +ALIVE = "*" +EMPTY = "-" + +class Grid: + def __init__(self, height, width): + self.height = height + self.width = width + self.rows = [] + for _ in range(self.height): + self.rows.append([EMPTY] * self.width) + + def get(self, y, x): + return self.rows[y % self.height][x % self.width] + + def set(self, y, x, state): + self.rows[y % self.height][x % self.width] = state + + def __str__(self): + output = "" + for row in self.rows: + for cell in row: + output += cell + output += "\n" + return output + + +def count_neighbors(y, x, get_cell): + n_ = get_cell(y - 1, x + 0) # North + ne = get_cell(y - 1, x + 1) # Northeast + e_ = get_cell(y + 0, x + 1) # East + se = get_cell(y + 1, x + 1) # Southeast + s_ = get_cell(y + 1, x + 0) # South + sw = get_cell(y + 1, x - 1) # Southwest + w_ = get_cell(y + 0, x - 1) # West + nw = get_cell(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +async def game_logic(state, neighbors): + # Do some input/output in here: + data = await my_socket.read(50) + + +async def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY + elif neighbors > 3: + return EMPTY + else: + if neighbors == 3: + return ALIVE + return state + + +print("Example 2") +async def step_cell(y, x, get_cell, set_cell): + state = get_cell(y, x) + neighbors = count_neighbors(y, x, get_cell) + next_state = await game_logic(state, neighbors) + set_cell(y, x, next_state) + + +print("Example 3") +import asyncio + +async def simulate(grid): + next_grid = Grid(grid.height, grid.width) + + tasks = [] + for y in range(grid.height): + for x in range(grid.width): + task = step_cell(y, x, grid.get, next_grid.set) # Fan-out + tasks.append(task) + + await asyncio.gather(*tasks) # Fan-in + + return next_grid + + +print("Example 4") +class ColumnPrinter: + def __init__(self): + self.columns = [] + + def append(self, data): + self.columns.append(data) + + def __str__(self): + row_count = 1 + for data in self.columns: + row_count = max(row_count, len(data.splitlines()) + 1) + + rows = [""] * row_count + for j in range(row_count): + for i, data in enumerate(self.columns): + line = data.splitlines()[max(0, j - 1)] + if j == 0: + padding = " " * (len(line) // 2) + rows[j] += padding + str(i) + padding + else: + rows[j] += line + + if (i + 1) < len(self.columns): + rows[j] += " | " + + return "\n".join(rows) + + +logging.getLogger().setLevel(logging.ERROR) + +grid = Grid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = asyncio.run(simulate(grid)) # Run the event loop + +print(columns) + +logging.getLogger().setLevel(logging.DEBUG) + + +print("Example 5") +async def count_neighbors(y, x, get_cell): + n_ = get_cell(y - 1, x + 0) # North + ne = get_cell(y - 1, x + 1) # Northeast + e_ = get_cell(y + 0, x + 1) # East + se = get_cell(y + 1, x + 1) # Southeast + s_ = get_cell(y + 1, x + 0) # South + sw = get_cell(y + 1, x - 1) # Southwest + w_ = get_cell(y + 0, x - 1) # West + nw = get_cell(y - 1, x - 1) # Northwest + neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] + count = 0 + for state in neighbor_states: + if state == ALIVE: + count += 1 + return count + +async def step_cell(y, x, get_cell, set_cell): + state = get_cell(y, x) + neighbors = await count_neighbors(y, x, get_cell) + next_state = await game_logic(state, neighbors) + set_cell(y, x, next_state) + +async def game_logic(state, neighbors): + if state == ALIVE: + if neighbors < 2: + return EMPTY + elif neighbors > 3: + return EMPTY + else: + if neighbors == 3: + return ALIVE + return state + +logging.getLogger().setLevel(logging.ERROR) + +grid = Grid(5, 9) +grid.set(0, 3, ALIVE) +grid.set(1, 4, ALIVE) +grid.set(2, 2, ALIVE) +grid.set(2, 3, ALIVE) +grid.set(2, 4, ALIVE) + +columns = ColumnPrinter() +for i in range(5): + columns.append(str(grid)) + grid = asyncio.run(simulate(grid)) + +print(columns) + +logging.getLogger().setLevel(logging.DEBUG) diff --git a/example_code/item_076.py b/example_code/item_076.py new file mode 100755 index 0000000..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_077.py b/example_code/item_077.py new file mode 100755 index 0000000..66867e1 --- /dev/null +++ b/example_code/item_077.py @@ -0,0 +1,305 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class NoNewData(Exception): + pass + +def readline(handle): + offset = handle.tell() + handle.seek(0, 2) + length = handle.tell() + + if length == offset: + raise NoNewData + + handle.seek(offset, 0) + return handle.readline() + + +print("Example 2") +import time + +def tail_file(handle, interval, write_func): + while not handle.closed: + try: + line = readline(handle) + except NoNewData: + time.sleep(interval) + else: + write_func(line) + + +print("Example 3") +from threading import Lock, Thread + +def run_threads(handles, interval, output_path): + with open(output_path, "wb") as output: + lock = Lock() + + def write(data): + with lock: + output.write(data) + + threads = [] + for handle in handles: + args = (handle, interval, write) + thread = Thread(target=tail_file, args=args) + thread.start() + threads.append(thread) + + for thread in threads: + thread.join() + + +print("Example 4") +# This is all code to simulate the writers to the handles +import collections +import os +import random +import string +from tempfile import TemporaryDirectory + +def write_random_data(path, write_count, interval): + with open(path, "wb") as f: + for i in range(write_count): + time.sleep(random.random() * interval) + letters = random.choices(string.ascii_lowercase, k=10) + data = f'{path}-{i:02}-{"".join(letters)}\n' + f.write(data.encode()) + f.flush() + +def start_write_threads(directory, file_count): + paths = [] + for i in range(file_count): + path = os.path.join(directory, str(i)) + with open(path, "w"): + # Make sure the file at this path will exist when + # the reading thread tries to poll it. + pass + paths.append(path) + args = (path, 10, 0.1) + thread = Thread(target=write_random_data, args=args) + thread.start() + return paths + +def close_all(handles): + time.sleep(1) + for handle in handles: + handle.close() + +def setup(): + tmpdir = TemporaryDirectory() + input_paths = start_write_threads(tmpdir.name, 5) + + handles = [] + for path in input_paths: + handle = open(path, "rb") + handles.append(handle) + + Thread(target=close_all, args=(handles,)).start() + + output_path = os.path.join(tmpdir.name, "merged") + return tmpdir, input_paths, handles, output_path + + +print("Example 5") +def confirm_merge(input_paths, output_path): + found = collections.defaultdict(list) + with open(output_path, "rb") as f: + for line in f: + for path in input_paths: + if line.find(path.encode()) == 0: + found[path].append(line) + + expected = collections.defaultdict(list) + for path in input_paths: + with open(path, "rb") as f: + expected[path].extend(f.readlines()) + + for key, expected_lines in expected.items(): + found_lines = found[key] + assert ( + expected_lines == found_lines + ), f"{expected_lines!r} == {found_lines!r}" + +input_paths = ... +handles = ... +output_path = ... + +tmpdir, input_paths, handles, output_path = setup() + +run_threads(handles, 0.1, output_path) + +confirm_merge(input_paths, output_path) + +tmpdir.cleanup() + + +print("Example 6") +import asyncio + +# TODO: Verify this is no longer needed +# +# On Windows, a ProactorEventLoop can't be created within +# threads because it tries to register signal handlers. This +# is a work-around to always use the SelectorEventLoop policy +# instead. See: https://bugs.python.org/issue33792 +# policy = asyncio.get_event_loop_policy() +# policy._loop_factory = asyncio.SelectorEventLoop +async def run_tasks_mixed(handles, interval, output_path): + loop = asyncio.get_event_loop() + + output = await loop.run_in_executor(None, open, output_path, "wb") + try: + + async def write_async(data): + await loop.run_in_executor(None, output.write, data) + + def write(data): + coro = write_async(data) + future = asyncio.run_coroutine_threadsafe(coro, loop) + future.result() + + tasks = [] + for handle in handles: + task = loop.run_in_executor( + None, tail_file, handle, interval, write + ) + tasks.append(task) + + await asyncio.gather(*tasks) + finally: + await loop.run_in_executor(None, output.close) + + +print("Example 7") +input_paths = ... +handles = ... +output_path = ... + +tmpdir, input_paths, handles, output_path = setup() + +asyncio.run(run_tasks_mixed(handles, 0.1, output_path)) + +confirm_merge(input_paths, output_path) + +tmpdir.cleanup() + + +print("Example 8") +async def tail_async(handle, interval, write_func): + loop = asyncio.get_event_loop() + + while not handle.closed: + try: + line = await loop.run_in_executor(None, readline, handle) + except NoNewData: + await asyncio.sleep(interval) + else: + await write_func(line) + + +print("Example 9") +async def run_tasks(handles, interval, output_path): + loop = asyncio.get_event_loop() + + output = await loop.run_in_executor(None, open, output_path, "wb") + try: + + async def write_async(data): + await loop.run_in_executor(None, output.write, data) + + async with asyncio.TaskGroup() as group: + for handle in handles: + group.create_task( + tail_async(handle, interval, write_async) + ) + finally: + await loop.run_in_executor(None, output.close) + + +print("Example 10") +input_paths = ... +handles = ... +output_path = ... + +tmpdir, input_paths, handles, output_path = setup() + +asyncio.run(run_tasks(handles, 0.1, output_path)) + +confirm_merge(input_paths, output_path) + +tmpdir.cleanup() + + +print("Example 11") +def tail_file(handle, interval, write_func): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + async def write_async(data): + await loop.run_in_executor(None, write_func, data) + + coro = tail_async(handle, interval, write_async) + loop.run_until_complete(coro) + + +print("Example 12") +input_paths = ... +handles = ... +output_path = ... + +tmpdir, input_paths, handles, output_path = setup() + +run_threads(handles, 0.1, output_path) + +confirm_merge(input_paths, output_path) + +tmpdir.cleanup() diff --git a/example_code/item_078.py b/example_code/item_078.py new file mode 100755 index 0000000..43d67ba --- /dev/null +++ b/example_code/item_078.py @@ -0,0 +1,262 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import asyncio + +async def run_tasks(handles, interval, output_path): + loop = asyncio.get_event_loop() + + output = await loop.run_in_executor(None, open, output_path, "wb") + try: + + async def write_async(data): + await loop.run_in_executor(None, output.write, data) + + async with asyncio.TaskGroup() as group: + for handle in handles: + group.create_task( + tail_async(handle, interval, write_async) + ) + finally: + await loop.run_in_executor(None, output.close) + + +print("Example 2") +async def run_tasks_simpler(handles, interval, output_path): + with open(output_path, "wb") as output: # Changed + + async def write_async(data): + output.write(data) # Changed + + async with asyncio.TaskGroup() as group: + for handle in handles: + group.create_task( + tail_async(handle, interval, write_async) + ) + + +print("Example 3") +import time + +async def slow_coroutine(): + time.sleep(0.5) # Simulating slow I/O + +asyncio.run(slow_coroutine(), debug=True) + + +print("Example 4") +from threading import Thread + +class WriteThread(Thread): + def __init__(self, output_path): + super().__init__() + self.output_path = output_path + self.output = None + self.loop = asyncio.new_event_loop() + + def run(self): + asyncio.set_event_loop(self.loop) + with open(self.output_path, "wb") as self.output: + self.loop.run_forever() + + # Run one final round of callbacks so the await on + # stop() in another event loop will be resolved. + self.loop.run_until_complete(asyncio.sleep(0)) + + + print("Example 5") + async def real_write(self, data): + self.output.write(data) + + async def write(self, data): + coro = self.real_write(data) + future = asyncio.run_coroutine_threadsafe( + coro, self.loop) + await asyncio.wrap_future(future) + + + print("Example 6") + async def real_stop(self): + self.loop.stop() + + async def stop(self): + coro = self.real_stop() + future = asyncio.run_coroutine_threadsafe( + coro, self.loop) + await asyncio.wrap_future(future) + + + print("Example 7") + async def __aenter__(self): + loop = asyncio.get_event_loop() + await loop.run_in_executor(None, self.start) + return self + + async def __aexit__(self, *_): + await self.stop() + + +print("Example 8") +class NoNewData(Exception): + pass + +def readline(handle): + offset = handle.tell() + handle.seek(0, 2) + length = handle.tell() + + if length == offset: + raise NoNewData + + handle.seek(offset, 0) + return handle.readline() + +async def tail_async(handle, interval, write_func): + loop = asyncio.get_event_loop() + + while not handle.closed: + try: + line = await loop.run_in_executor(None, readline, handle) + except NoNewData: + await asyncio.sleep(interval) + else: + await write_func(line) + +async def run_fully_async(handles, interval, output_path): + async with ( + WriteThread(output_path) as output, + asyncio.TaskGroup() as group, + ): + for handle in handles: + group.create_task( + tail_async(handle, interval, output.write) + ) + + +print("Example 9") +# This is all code to simulate the writers to the handles +import collections +import os +import random +import string +from tempfile import TemporaryDirectory + +def write_random_data(path, write_count, interval): + with open(path, "wb") as f: + for i in range(write_count): + time.sleep(random.random() * interval) + letters = random.choices(string.ascii_lowercase, k=10) + data = f'{path}-{i:02}-{"".join(letters)}\n' + f.write(data.encode()) + f.flush() + +def start_write_threads(directory, file_count): + paths = [] + for i in range(file_count): + path = os.path.join(directory, str(i)) + with open(path, "w"): + # Make sure the file at this path will exist when + # the reading thread tries to poll it. + pass + paths.append(path) + args = (path, 10, 0.1) + thread = Thread(target=write_random_data, args=args) + thread.start() + return paths + +def close_all(handles): + time.sleep(1) + for handle in handles: + handle.close() + +def setup(): + tmpdir = TemporaryDirectory() + input_paths = start_write_threads(tmpdir.name, 5) + + handles = [] + for path in input_paths: + handle = open(path, "rb") + handles.append(handle) + + Thread(target=close_all, args=(handles,)).start() + + output_path = os.path.join(tmpdir.name, "merged") + return tmpdir, input_paths, handles, output_path + + +print("Example 10") +def confirm_merge(input_paths, output_path): + found = collections.defaultdict(list) + with open(output_path, "rb") as f: + for line in f: + for path in input_paths: + if line.find(path.encode()) == 0: + found[path].append(line) + + expected = collections.defaultdict(list) + for path in input_paths: + with open(path, "rb") as f: + expected[path].extend(f.readlines()) + + for key, expected_lines in expected.items(): + found_lines = found[key] + assert expected_lines == found_lines + +input_paths = ... +handles = ... +output_path = ... + +tmpdir, input_paths, handles, output_path = setup() + +asyncio.run(run_fully_async(handles, 0.1, output_path)) + +confirm_merge(input_paths, output_path) + +tmpdir.cleanup() diff --git a/example_code/item_01.py b/example_code/item_079/parallel/my_module.py similarity index 65% rename from example_code/item_01.py rename to example_code/item_079/parallel/my_module.py index cc98001..a61a84b 100755 --- a/example_code/item_01.py +++ b/example_code/item_079/parallel/my_module.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -import sys -print(sys.version_info) -print(sys.version) +def gcd(pair): + a, b = pair + low = min(a, b) + for i in range(low, 0, -1): + if a % i == 0 and b % i == 0: + return i + raise RuntimeError("Not reachable") diff --git a/example_code/item_079/parallel/run_parallel.py b/example_code/item_079/parallel/run_parallel.py new file mode 100755 index 0000000..4103f2a --- /dev/null +++ b/example_code/item_079/parallel/run_parallel.py @@ -0,0 +1,43 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import my_module +from concurrent.futures import ProcessPoolExecutor +import time + +NUMBERS = [ + (19633090, 22659730), + (20306770, 38141720), + (15516450, 22296200), + (20390450, 20208020), + (18237120, 19249280), + (22931290, 10204910), + (12812380, 22737820), + (38238120, 42372810), + (38127410, 47291390), + (12923910, 21238110), +] + +def main(): + start = time.perf_counter() + pool = ProcessPoolExecutor(max_workers=8) # The one change + results = list(pool.map(my_module.gcd, NUMBERS)) + end = time.perf_counter() + delta = end - start + print(f"Took {delta:.3f} seconds") + +if __name__ == "__main__": + main() diff --git a/example_code/item_079/parallel/run_serial.py b/example_code/item_079/parallel/run_serial.py new file mode 100755 index 0000000..ba218bf --- /dev/null +++ b/example_code/item_079/parallel/run_serial.py @@ -0,0 +1,41 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import my_module +import time + +NUMBERS = [ + (19633090, 22659730), + (20306770, 38141720), + (15516450, 22296200), + (20390450, 20208020), + (18237120, 19249280), + (22931290, 10204910), + (12812380, 22737820), + (38238120, 42372810), + (38127410, 47291390), + (12923910, 21238110), +] + +def main(): + start = time.perf_counter() + results = list(map(my_module.gcd, NUMBERS)) + end = time.perf_counter() + delta = end - start + print(f"Took {delta:.3f} seconds") + +if __name__ == "__main__": + main() diff --git a/example_code/item_079/parallel/run_threads.py b/example_code/item_079/parallel/run_threads.py new file mode 100755 index 0000000..3eebc1a --- /dev/null +++ b/example_code/item_079/parallel/run_threads.py @@ -0,0 +1,43 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import my_module +from concurrent.futures import ThreadPoolExecutor +import time + +NUMBERS = [ + (19633090, 22659730), + (20306770, 38141720), + (15516450, 22296200), + (20390450, 20208020), + (18237120, 19249280), + (22931290, 10204910), + (12812380, 22737820), + (38238120, 42372810), + (38127410, 47291390), + (12923910, 21238110), +] + +def main(): + start = time.perf_counter() + pool = ThreadPoolExecutor(max_workers=8) + results = list(pool.map(my_module.gcd, NUMBERS)) + end = time.perf_counter() + delta = end - start + print(f"Took {delta:.3f} seconds") + +if __name__ == "__main__": + main() diff --git a/example_code/item_080.py b/example_code/item_080.py new file mode 100755 index 0000000..743f652 --- /dev/null +++ b/example_code/item_080.py @@ -0,0 +1,198 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def try_finally_example(filename): + print("* Opening file") + handle = open(filename, encoding="utf-8") # May raise OSError + try: + print("* Reading data") + return handle.read() # May raise UnicodeDecodeError + finally: + print("* Calling close()") + handle.close() # Always runs after try block + + +print("Example 2") +try: + filename = "random_data.txt" + + with open(filename, "wb") as f: + f.write(b"\xf1\xf2\xf3\xf4\xf5") # Invalid utf-8 + + data = try_finally_example(filename) + # This should not be reached. + import sys + + sys.exit(1) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +try: + try_finally_example("does_not_exist.txt") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 4") +import json + +def load_json_key(data, key): + try: + print("* Loading JSON data") + result_dict = json.loads(data) # May raise ValueError + except ValueError: + print("* Handling ValueError") + raise KeyError(key) + else: + print("* Looking up key") + return result_dict[key] # May raise KeyError + + +print("Example 5") +assert load_json_key('{"foo": "bar"}', "foo") == "bar" + + +print("Example 6") +try: + load_json_key('{"foo": bad payload', "foo") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 7") +try: + load_json_key('{"foo": "bar"}', "does not exist") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +UNDEFINED = object() +DIE_IN_ELSE_BLOCK = False + +def divide_json(path): + print("* Opening file") + handle = open(path, "r+") # May raise OSError + try: + print("* Reading data") + data = handle.read() # May raise UnicodeDecodeError + print("* Loading JSON data") + op = json.loads(data) # May raise ValueError + print("* Performing calculation") + value = op["numerator"] / op["denominator"] # May raise ZeroDivisionError + except ZeroDivisionError: + print("* Handling ZeroDivisionError") + return UNDEFINED + else: + print("* Writing calculation") + op["result"] = value + result = json.dumps(op) + handle.seek(0) # May raise OSError + if DIE_IN_ELSE_BLOCK: + import errno + import os + + raise OSError(errno.ENOSPC, os.strerror(errno.ENOSPC)) + handle.write(result) # May raise OSError + return value + finally: + print("* Calling close()") + handle.close() # Always runs + + +print("Example 9") +temp_path = "random_data.json" + +with open(temp_path, "w") as f: + f.write('{"numerator": 1, "denominator": 10}') + +assert divide_json(temp_path) == 0.1 + + +print("Example 10") +with open(temp_path, "w") as f: + f.write('{"numerator": 1, "denominator": 0}') + +assert divide_json(temp_path) is UNDEFINED + + +print("Example 11") +try: + with open(temp_path, "w") as f: + f.write('{"numerator": 1 bad data') + + divide_json(temp_path) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 12") +try: + with open(temp_path, "w") as f: + f.write('{"numerator": 1, "denominator": 10}') + DIE_IN_ELSE_BLOCK = True + + divide_json(temp_path) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_081.py b/example_code/item_081.py new file mode 100755 index 0000000..51747ef --- /dev/null +++ b/example_code/item_081.py @@ -0,0 +1,138 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + list_a = [1, 2, 3] + assert list_a, "a empty" + list_b = [] + assert list_b, "b empty" # Raises +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +try: + class EmptyError(Exception): + pass + + list_c = [] + if not list_c: + raise EmptyError("c empty") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +try: + raise EmptyError("From raise statement") +except EmptyError as e: + print(f"Caught: {e}") + + +print("Example 4") +try: + assert False, "From assert statement" +except AssertionError as e: + print(f"Caught: {e}") + + +print("Example 5") +class RatingError(Exception): + pass + +class Rating: + def __init__(self, max_rating): + if not (max_rating > 0): + raise RatingError("Invalid max_rating") + self.max_rating = max_rating + self.ratings = [] + + def rate(self, rating): + if not (0 < rating <= self.max_rating): + raise RatingError("Invalid rating") + self.ratings.append(rating) + + +print("Example 6") +try: + movie = Rating(5) + movie.rate(5) + movie.rate(7) # Raises +except: + logging.exception('Expected') +else: + assert False + + +print("Example 7") +class RatingInternal: + def __init__(self, max_rating): + assert max_rating > 0, f"Invalid {max_rating=}" + self.max_rating = max_rating + self.ratings = [] + + def rate(self, rating): + assert 0 < rating <= self.max_rating, f"Invalid {rating=}" + self.ratings.append(rating) + + +print("Example 8") +try: + movie = RatingInternal(5) + movie.rate(5) + movie.rate(7) # Raises +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_082.py b/example_code/item_082.py new file mode 100755 index 0000000..d1c594a --- /dev/null +++ b/example_code/item_082.py @@ -0,0 +1,146 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +from threading import Lock + +lock = Lock() +with lock: + # Do something while maintaining an invariant + pass + + +print("Example 2") +lock.acquire() +try: + # Do something while maintaining an invariant + pass +finally: + lock.release() + + +print("Example 3") +import logging + +logging.getLogger().setLevel(logging.WARNING) + +def my_function(): + logging.debug("Some debug data") + logging.error("Error log here") + logging.debug("More debug data") + + +print("Example 4") +my_function() + + +print("Example 5") +from contextlib import contextmanager + +@contextmanager +def debug_logging(level): + logger = logging.getLogger() + old_level = logger.getEffectiveLevel() + logger.setLevel(level) + try: + yield + finally: + logger.setLevel(old_level) + + +print("Example 6") +with debug_logging(logging.DEBUG): + print("* Inside:") + my_function() + +print("* After:") +my_function() + + +print("Example 7") +with open("my_output.txt", "w") as handle: + handle.write("This is some data!") + + +print("Example 8") +handle = open("my_output.txt", "w") +try: + handle.write("This is some data!") +finally: + handle.close() + + +print("Example 9") +@contextmanager +def log_level(level, name): + logger = logging.getLogger(name) + old_level = logger.getEffectiveLevel() + logger.setLevel(level) + try: + yield logger + finally: + logger.setLevel(old_level) + + +print("Example 10") +with log_level(logging.DEBUG, "my-log") as my_logger: + my_logger.debug(f"This is a message for {my_logger.name}!") + logging.debug("This will not print") + + +print("Example 11") +logger = logging.getLogger("my-log") +logger.debug("Debug will not print") +logger.error("Error will print") + + +print("Example 12") +with log_level(logging.DEBUG, "other-log") as my_logger: # Changed + my_logger.debug(f"This is a message for {my_logger.name}!") + logging.debug("This will not print") diff --git a/example_code/item_083.py b/example_code/item_083.py new file mode 100755 index 0000000..b3637db --- /dev/null +++ b/example_code/item_083.py @@ -0,0 +1,108 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +connection = ... + +class RpcError(Exception): + pass + +def lookup_request(connection): + raise RpcError("From lookup_request") + +def close_connection(connection): + print("Connection closed") + +try: + request = lookup_request(connection) +except RpcError: + print("Encountered error!") + close_connection(connection) + + +print("Example 2") +def lookup_request(connection): + # No error raised + return object() + +def is_cached(connection, request): + raise RpcError("From is_cached") + +try: + request = lookup_request(connection) + if is_cached(connection, request): + request = None +except RpcError: + print("Encountered error!") + close_connection(connection) + + +print("Example 3") +def is_closed(_): + pass + +if is_closed(connection): + # Was the connection closed because of an error + # in lookup_request or is_cached? + pass + + +print("Example 4") +try: + try: + request = lookup_request(connection) + except RpcError: + close_connection(connection) + else: + if is_cached(connection, request): # Moved + request = None +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_084.py b/example_code/item_084.py new file mode 100755 index 0000000..9a9f12e --- /dev/null +++ b/example_code/item_084.py @@ -0,0 +1,112 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + class MyError(Exception): + pass + + try: + raise MyError(123) + except MyError as e: + print(f"Inside {e=}") + + print(f"Outside {e=}") # Raises +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +try: + try: + raise MyError(123) + except MyError as e: + print(f"Inside {e=}") + finally: + print(f"Finally {e=}") # Raises +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +class OtherError(Exception): + pass + +result = "Unexpected exception" +try: + raise MyError(123) +except MyError as e: + result = e +except OtherError as e: + result = e +else: + result = "Success" +finally: + print(f"Log {result=}") + + +print("Example 4") +try: + del result + try: + raise OtherError(123) # Not handled + except MyError as e: + result = e + else: + result = "Success" + finally: + print(f"{result=}") # Raises +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_085.py b/example_code/item_085.py new file mode 100755 index 0000000..55f713b --- /dev/null +++ b/example_code/item_085.py @@ -0,0 +1,109 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def load_data(path): + open(path).read() + +def analyze_data(data): + return "my summary" + +def run_report(path): + data = load_data(path) + summary = analyze(data) + return summary + + +print("Example 2") +try: + summary = run_report("pizza_data-2024-01-28.csv") + print(summary) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +try: + summary = run_report("pizza_data.csv") +except FileNotFoundError: + print("Transient file error") +else: + print(summary) + + +print("Example 4") +try: + summary = run_report("pizza_data.csv") +except Exception: # Changed + print("Transient report issue") +else: + print(summary) + + +print("Example 5") +try: + def load_data(path): + pass + + run_report("my_data.csv") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 6") +try: + summary = run_report("my_data.csv") +except Exception as e: + print("Fail:", type(e), e) +else: + print(summary) diff --git a/example_code/item_086.py b/example_code/item_086.py new file mode 100755 index 0000000..1c52390 --- /dev/null +++ b/example_code/item_086.py @@ -0,0 +1,249 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + def do_processing(): + raise KeyboardInterrupt + + def main(argv): + while True: + try: + do_processing() # Interrupted + except Exception as e: + print("Error:", type(e), e) + + return 0 + + if __name__ == "__main__": + sys.exit(main(sys.argv)) + else: + main(["foo.csv"]) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +try: + with open("my_data.csv", "w") as f: + f.write("file exists") + + def do_processing(handle): + raise KeyboardInterrupt + + def main(argv): + data_path = argv[1] + handle = open(data_path, "w+") + + while True: + try: + do_processing(handle) + except Exception as e: + print("Error:", type(e), e) + except BaseException: + print("Cleaning up interrupt") + handle.flush() + handle.close() + return 1 + + return 0 + + if __name__ == "__main__": + sys.exit(main(sys.argv)) + else: + main(["ignore", "foo.csv"]) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +try: + def do_processing(handle): + raise KeyboardInterrupt + + def main(argv): + data_path = argv[1] + handle = open(data_path, "w+") + + try: + while True: + try: + do_processing(handle) + except Exception as e: + print("Error:", type(e), e) + finally: + print("Cleaning up finally") # Always runs + handle.flush() + handle.close() + + if __name__ == "__main__": + sys.exit(main(sys.argv)) + else: + main(["ignore", "foo.csv"]) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 4") +try: + def do_processing(): + raise KeyboardInterrupt + + def input(prompt): + print(f"{prompt}y") + return "y" + + def main(argv): + while True: + try: + do_processing() + except Exception as e: + print("Error:", type(e), e) + except KeyboardInterrupt: + found = input("Terminate? [y/n]: ") + if found == "y": + raise # Propagate the error + + if __name__ == "__main__": + sys.exit(main(sys.argv)) + else: + main(["ignore", "foo.csv"]) + + del input +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +import functools + +def log(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + try: + result = func(*args, **kwargs) + except Exception as e: + result = e + raise + finally: + print( + f"Called {func.__name__}" + f"(*{args!r}, **{kwargs!r}) " + f"got {result!r}" + ) + + return wrapper + + +print("Example 6") +try: + @log + def my_func(x): + x / 0 + + my_func(123) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 7") +try: + @log + def other_func(x): + if x > 0: + sys.exit(1) + + other_func(456) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +def fixed_log(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + try: + result = func(*args, **kwargs) + except BaseException as e: # Fixed + result = e + raise + finally: + print( + f"Called {func.__name__}" + f"(*{args!r}, **{kwargs!r}) " + f"got {result!r}" + ) + + return wrapper + + +print("Example 9") +try: + @fixed_log + def other_func(x): + if x > 0: + sys.exit(1) + + other_func(456) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_087.py b/example_code/item_087.py new file mode 100755 index 0000000..39ee62e --- /dev/null +++ b/example_code/item_087.py @@ -0,0 +1,129 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class Request: + def __init__(self, body): + self.body = body + self.response = None + +def do_work(data): + assert False, data + +def handle(request): + try: + do_work(request.body) + except BaseException as e: + print(repr(e)) + request.response = 400 # Bad request error + + +print("Example 2") +request = Request("My message") +handle(request) + + +print("Example 3") +import traceback + +def handle2(request): + try: + do_work(request.body) + except BaseException as e: + traceback.print_tb(e.__traceback__) # Changed + print(repr(e)) + request.response = 400 + +request = Request("My message 2") +handle2(request) + + +print("Example 4") +def handle3(request): + try: + do_work(request.body) + except BaseException as e: + stack = traceback.extract_tb(e.__traceback__) + for frame in stack: + print(frame.name) + print(repr(e)) + request.response = 400 + +request = Request("My message 3") +handle3(request) + + +print("Example 5") +import json + +def log_if_error(file_path, target, *args, **kwargs): + try: + target(*args, **kwargs) + except BaseException as e: + stack = traceback.extract_tb(e.__traceback__) + stack_without_wrapper = stack[1:] + trace_dict = dict( + stack=[item.name for item in stack_without_wrapper], + error_type=type(e).__name__, + error_message=str(e), + ) + json_data = json.dumps(trace_dict) + + with open(file_path, "a") as f: + f.write(json_data) + f.write("\n") + + +print("Example 6") +log_if_error("my_log.jsonl", do_work, "First error") +log_if_error("my_log.jsonl", do_work, "Second error") + +with open("my_log.jsonl") as f: + for line in f: + print(line, end="") diff --git a/example_code/item_088.py b/example_code/item_088.py new file mode 100755 index 0000000..d30e278 --- /dev/null +++ b/example_code/item_088.py @@ -0,0 +1,256 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + my_dict = {} + my_dict["does_not_exist"] +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +my_dict = {} +try: + my_dict["does_not_exist"] +except KeyError: + print("Could not find key!") + + +print("Example 3") +try: + class MissingError(Exception): + pass + + try: + my_dict["does_not_exist"] # Raises first exception + except KeyError: + raise MissingError("Oops!") # Raises second exception +except: + logging.exception('Expected') +else: + assert False + + +print("Example 4") +try: + try: + my_dict["does_not_exist"] + except KeyError: + raise MissingError("Oops!") +except MissingError as e: + print("Second:", repr(e)) + print("First: ", repr(e.__context__)) + + +print("Example 5") +def lookup(my_key): + try: + return my_dict[my_key] + except KeyError: + raise MissingError + + +print("Example 6") +my_dict["my key 1"] = 123 +print(lookup("my key 1")) + + +print("Example 7") +try: + print(lookup("my key 2")) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +def contact_server(my_key): + print(f"Looking up {my_key!r} in server") + return "my value 2" + +def lookup(my_key): + try: + return my_dict[my_key] + except KeyError: + result = contact_server(my_key) + my_dict[my_key] = result # Fill the local cache + return result + + +print("Example 9") +print("Call 1") +print("Result:", lookup("my key 2")) +print("Call 2") +print("Result:", lookup("my key 2")) + + +print("Example 10") +class ServerMissingKeyError(Exception): + pass + +def contact_server(my_key): + print(f"Looking up {my_key!r} in server") + raise ServerMissingKeyError + + +print("Example 11") +try: + print(lookup("my key 3")) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 12") +def lookup(my_key): + try: + return my_dict[my_key] + except KeyError: + try: + result = contact_server(my_key) + except ServerMissingKeyError: + raise MissingError # Convert the server error + else: + my_dict[my_key] = result # Fill the local cache + return result + + +print("Example 13") +try: + print(lookup("my key 4")) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 14") +def lookup_explicit(my_key): + try: + return my_dict[my_key] + except KeyError as e: # Changed + try: + result = contact_server(my_key) + except ServerMissingKeyError: + raise MissingError from e # Changed + else: + my_dict[my_key] = result + return result + + +print("Example 15") +try: + print(lookup_explicit("my key 5")) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 16") +try: + lookup_explicit("my key 6") +except Exception as e: + print("Exception:", repr(e)) + print("Context: ", repr(e.__context__)) + print("Cause: ", repr(e.__cause__)) + print("Suppress: ", repr(e.__suppress_context__)) + + +print("Example 17") +import traceback + +try: + lookup("my key 7") +except Exception as e: + stack = traceback.extract_tb(e.__traceback__) + for frame in stack: + print(frame.line) + + +print("Example 18") +def get_cause(exc): + if exc.__cause__ is not None: + return exc.__cause__ + elif not exc.__suppress_context__: + return exc.__context__ + else: + return None + + +print("Example 19") +try: + lookup("my key 8") +except Exception as e: + while e is not None: + stack = traceback.extract_tb(e.__traceback__) + for i, frame in enumerate(stack, 1): + print(i, frame.line) + e = get_cause(e) + if e: + print("Caused by") + + +print("Example 20") +def contact_server(key): + raise ServerMissingKeyError from None # Suppress + + +print("Example 21") +try: + print(lookup("my key 9")) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_089.py b/example_code/item_089.py new file mode 100755 index 0000000..188905a --- /dev/null +++ b/example_code/item_089.py @@ -0,0 +1,171 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def my_func(): + try: + return 123 + finally: + print("Finally my_func") + +print("Before") +print(my_func()) +print("After") + + +print("Example 2") +def my_generator(): + try: + yield 10 + yield 20 + yield 30 + finally: + print("Finally my_generator") + +print("Before") + +for i in my_generator(): + print(i) + +print("After") + + +print("Example 3") +it = my_generator() +print("Before") +print(next(it)) +print(next(it)) +print("After") + + +print("Example 4") +import gc + +del it +gc.collect() + + +print("Example 5") +def catching_generator(): + try: + yield 40 + yield 50 + yield 60 + except BaseException as e: # Catches GeneratorExit + print("Catching handler", type(e), e) + raise + + +print("Example 6") +it = catching_generator() +print("Before") +print(next(it)) +print(next(it)) +print("After") +del it +gc.collect() + + +print("Example 8") +with open("my_file.txt", "w") as f: + for _ in range(20): + f.write("a" * random.randint(0, 100)) + f.write("\n") + +def lengths_path(path): + try: + with open(path) as handle: + for i, line in enumerate(handle): + print(f"Line {i}") + yield len(line.strip()) + finally: + print("Finally lengths_path") + + +print("Example 9") +max_head = 0 +it = lengths_path("my_file.txt") + +for i, length in enumerate(it): + if i == 5: + break + else: + max_head = max(max_head, length) + +print(max_head) + + +print("Example 10") +del it +gc.collect() + + +print("Example 11") +def lengths_handle(handle): + try: + for i, line in enumerate(handle): + print(f"Line {i}") + yield len(line.strip()) + finally: + print("Finally lengths_handle") + + +print("Example 12") +max_head = 0 + +with open("my_file.txt") as handle: + it = lengths_handle(handle) + for i, length in enumerate(it): + if i == 5: + break + else: + max_head = max(max_head, length) + +print(max_head) +print("Handle closed:", handle.closed) diff --git a/example_code/item_54.py b/example_code/item_089_example_07.py similarity index 56% rename from example_code/item_54.py rename to example_code/item_089_example_07.py index 2973d5a..88341fd 100755 --- a/example_code/item_54.py +++ b/example_code/item_089_example_07.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,23 +14,25 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT -# Example 4 -# db_connection.py +print("Example 7") +import gc import sys -class Win32Database(object): - pass +def broken_generator(): + try: + yield 70 + yield 80 + except BaseException as e: + print("Broken handler", type(e), e) + raise RuntimeError("Broken") -class PosixDatabase(object): - pass - -if sys.platform.startswith('win32'): - Database = Win32Database -else: - Database = PosixDatabase +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_09.py b/example_code/item_09.py deleted file mode 100755 index 6d3a41a..0000000 --- a/example_code/item_09.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -import random -with open('my_file.txt', 'w') as f: - for _ in range(10): - f.write('a' * random.randint(0, 100)) - f.write('\n') - -value = [len(x) for x in open('my_file.txt')] -print(value) - - -# Example 2 -it = (len(x) for x in open('my_file.txt')) -print(it) - - -# Example 3 -print(next(it)) -print(next(it)) - - -# Example 4 -roots = ((x, x**0.5) for x in it) - - -# Example 5 -print(next(roots)) diff --git a/example_code/item_090.py b/example_code/item_090.py new file mode 100755 index 0000000..83e8829 --- /dev/null +++ b/example_code/item_090.py @@ -0,0 +1,94 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + n = 3 + assert n % 2 == 0, f"{n=} not even" +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +try: + if __debug__: + if not (n % 2 == 0): + raise AssertionError(f"{n=} not even") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +try: + def expensive_check(x): + return x != 2 + + items = [1, 2, 3] + if __debug__: + for i in items: + assert expensive_check(i), f"Failed {i=}" +except: + logging.exception('Expected') +else: + assert False + + +print("Example 4") +try: + # This will not compile + source = """__debug__ = False""" + eval(source) +except: + logging.exception('Expected') +else: + assert False diff --git a/example_code/item_091.py b/example_code/item_091.py new file mode 100755 index 0000000..00f02a0 --- /dev/null +++ b/example_code/item_091.py @@ -0,0 +1,86 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +x = eval("1 + 2") +print(x) + + +print("Example 2") +try: + eval( + """ + if True: + print('okay') + else: + print('no') + """ + ) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +global_scope = {"my_condition": False} +local_scope = {} + +exec( + """ +if my_condition: + x = 'yes' +else: + x = 'no' +""", + global_scope, + local_scope, +) + +print(local_scope) diff --git a/example_code/item_58.py b/example_code/item_092.py similarity index 63% rename from example_code/item_58.py rename to example_code/item_092.py index 1c64e6e..92f7c35 100755 --- a/example_code/item_58.py +++ b/example_code/item_092.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,40 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment +### 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 + -# Example 1 +print("Example 1") def insertion_sort(data): result = [] for value in data: @@ -28,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: @@ -37,48 +64,49 @@ 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 -import sys +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): i = bisect_left(array, value) array.insert(i, value) + +print("Example 7") profiler = Profile() profiler.runcall(test) stats = Stats(profiler, stream=STDOUT) stats.strip_dirs() -stats.sort_stats('cumulative') +stats.sort_stats("cumulative") stats.print_stats() -# Example 7 +print("Example 8") def my_utility(a, b): c = 1 for i in range(100): @@ -98,17 +126,24 @@ def my_program(): second_func() -# Example 8 +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 9 +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_50/api_package/mypackage/__init__.py b/example_code/item_096/my_extension2/setup.py old mode 100755 new mode 100644 similarity index 63% rename from example_code/item_50/api_package/mypackage/__init__.py rename to example_code/item_096/my_extension2/setup.py index 3e55261..529b231 --- a/example_code/item_50/api_package/mypackage/__init__.py +++ b/example_code/item_096/my_extension2/setup.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,15 +14,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT +from setuptools import Extension, setup - -# Example 11 -__all__ = [] -from . models import * -__all__ += models.__all__ -from . utils import * -__all__ += utils.__all__ +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_03_example_03.py b/example_code/item_098/mycli/mycli.py similarity index 54% rename from example_code/item_03_example_03.py rename to example_code/item_098/mycli/mycli.py index 344edab..8f28411 100755 --- a/example_code/item_03_example_03.py +++ b/example_code/item_098/mycli/mycli.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,19 +14,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT +# mycli.py +import adjust +import enhance +import parser +def main(): + args = parser.PARSER.parse_args() -# Example 3 -def to_unicode(unicode_or_str): - if isinstance(unicode_or_str, str): - value = unicode_or_str.decode('utf-8') + 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: - value = unicode_or_str - return value # Instance of unicode + raise RuntimeError("Not reachable") -print(repr(to_unicode(u'foo'))) -print(repr(to_unicode('foo'))) +if __name__ == "__main__": + main() diff --git a/example_code/item_098/mycli/mycli_faster.py b/example_code/item_098/mycli/mycli_faster.py new file mode 100755 index 0000000..edc5126 --- /dev/null +++ b/example_code/item_098/mycli/mycli_faster.py @@ -0,0 +1,35 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# mycli_faster.py +import parser + +def main(): + args = parser.PARSER.parse_args() + + if args.command == "enhance": + import enhance # Changed + + enhance.do_enhance(args.file, args.amount) + elif args.command == "adjust": + import adjust # Changed + + adjust.do_adjust(args.file, args.brightness, args.contrast) + else: + raise RuntimeError("Not reachable") + +if __name__ == "__main__": + main() diff --git a/example_code/item_098/mycli/parser.py b/example_code/item_098/mycli/parser.py new file mode 100755 index 0000000..6b4dbff --- /dev/null +++ b/example_code/item_098/mycli/parser.py @@ -0,0 +1,30 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# parser.py +import argparse + +PARSER = argparse.ArgumentParser() +PARSER.add_argument("file") + +sub_parsers = PARSER.add_subparsers(dest="command") + +enhance_parser = sub_parsers.add_parser("enhance") +enhance_parser.add_argument("--amount", type=float) + +adjust_parser = sub_parsers.add_parser("adjust") +adjust_parser.add_argument("--brightness", type=float) +adjust_parser.add_argument("--contrast", type=float) diff --git a/example_code/item_098/mycli/server.py b/example_code/item_098/mycli/server.py new file mode 100755 index 0000000..5629a96 --- /dev/null +++ b/example_code/item_098/mycli/server.py @@ -0,0 +1,43 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# server.py +from flask import Flask, render_template, request + +app = Flask(__name__) + +@app.route("/adjust", methods=["GET", "POST"]) +def do_adjust(): + if request.method == "POST": + the_file = request.files["the_file"] + brightness = request.form["brightness"] + contrast = request.form["contrast"] + import adjust # Dynamic import + + return adjust.do_adjust(the_file, brightness, contrast) + else: + return render_template("adjust.html") + +@app.route("/enhance", methods=["GET", "POST"]) +def do_enhance(): + if request.method == "POST": + the_file = request.files["the_file"] + amount = request.form["amount"] + import enhance # Dynamic import + + return enhance.do_enhance(the_file, amount) + else: + return render_template("enhance.html") diff --git a/example_code/item_099.py b/example_code/item_099.py new file mode 100755 index 0000000..0819f15 --- /dev/null +++ b/example_code/item_099.py @@ -0,0 +1,225 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def timecode_to_index(video_id, timecode): + return 1234 + # Returns the byte offset in the video data + +def request_chunk(video_id, byte_offset, size): + pass + # Returns size bytes of video_id's data from the offset + +video_id = ... +timecode = "01:09:14:28" +byte_offset = timecode_to_index(video_id, timecode) +size = 20 * 1024 * 1024 +video_data = request_chunk(video_id, byte_offset, size) + + +print("Example 2") +class NullSocket: + def __init__(self): + self.handle = open(os.devnull, "wb") + + def send(self, data): + self.handle.write(data) + +socket = ... # socket connection to client +video_data = ... # bytes containing data for video_id +byte_offset = ... # Requested starting position +size = 20 * 1024 * 1024 # Requested chunk size +import os + +socket = NullSocket() +video_data = 100 * os.urandom(1024 * 1024) +byte_offset = 1234 + +chunk = video_data[byte_offset : byte_offset + size] +socket.send(chunk) + + +print("Example 3") +import timeit + +def run_test(): + chunk = video_data[byte_offset : byte_offset + size] + # Call socket.send(chunk), but ignoring for benchmark + +result = ( + timeit.timeit( + stmt="run_test()", + globals=globals(), + number=100, + ) + / 100 +) + +print(f"{result:0.9f} seconds") + + +print("Example 4") +data = b"shave and a haircut, two bits" +view = memoryview(data) +chunk = view[12:19] +print(chunk) +print("Size: ", chunk.nbytes) +print("Data in view: ", chunk.tobytes()) +print("Underlying data:", chunk.obj) + + +print("Example 5") +video_view = memoryview(video_data) + +def run_test(): + chunk = video_view[byte_offset : byte_offset + size] + # Call socket.send(chunk), but ignoring for benchmark + +result = ( + timeit.timeit( + stmt="run_test()", + globals=globals(), + number=100, + ) + / 100 +) + +print(f"{result:0.9f} seconds") + + +print("Example 6") +class FakeSocket: + + def recv(self, size): + return video_view[byte_offset : byte_offset + size] + + def recv_into(self, buffer): + source_data = video_view[byte_offset : byte_offset + size] + buffer[:] = source_data + +socket = ... # socket connection to the client +video_cache = ... # Cache of incoming video stream +byte_offset = ... # Incoming buffer position +size = 1024 * 1024 # Incoming chunk size +socket = FakeSocket() +video_cache = video_data[:] +byte_offset = 1234 + +chunk = socket.recv(size) +video_view = memoryview(video_cache) +before = video_view[:byte_offset] +after = video_view[byte_offset + size :] +new_cache = b"".join([before, chunk, after]) + + +print("Example 7") +def run_test(): + chunk = socket.recv(size) + before = video_view[:byte_offset] + after = video_view[byte_offset + size :] + new_cache = b"".join([before, chunk, after]) + +result = ( + timeit.timeit( + stmt="run_test()", + globals=globals(), + number=100, + ) + / 100 +) + +print(f"{result:0.9f} seconds") + + +print("Example 8") +try: + my_bytes = b"hello" + my_bytes[0] = 0x79 +except: + logging.exception('Expected') +else: + assert False + + +print("Example 9") +my_array = bytearray(b"hello") +my_array[0] = 0x79 +print(my_array) + + +print("Example 10") +my_array = bytearray(b"row, row, row your boat") +my_view = memoryview(my_array) +write_view = my_view[3:13] +write_view[:] = b"-10 bytes-" +print(my_array) + + +print("Example 11") +video_array = bytearray(video_cache) +write_view = memoryview(video_array) +chunk = write_view[byte_offset : byte_offset + size] +socket.recv_into(chunk) + + +print("Example 12") +def run_test(): + chunk = write_view[byte_offset : byte_offset + size] + socket.recv_into(chunk) + +result = ( + timeit.timeit( + stmt="run_test()", + globals=globals(), + number=100, + ) + / 100 +) + +print(f"{result:0.9f} seconds") diff --git a/example_code/item_10.py b/example_code/item_10.py deleted file mode 100755 index fb244be..0000000 --- a/example_code/item_10.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -from random import randint -random_bits = 0 -for i in range(64): - if randint(0, 1): - random_bits |= 1 << i -print(bin(random_bits)) - - -# Example 2 -flavor_list = ['vanilla', 'chocolate', 'pecan', 'strawberry'] -for flavor in flavor_list: - print('%s is delicious' % flavor) - - -# Example 3 -for i in range(len(flavor_list)): - flavor = flavor_list[i] - print('%d: %s' % (i + 1, flavor)) - - -# Example 4 -for i, flavor in enumerate(flavor_list): - print('%d: %s' % (i + 1, flavor)) - - -# Example 5 -for i, flavor in enumerate(flavor_list, 1): - print('%d: %s' % (i, flavor)) diff --git a/example_code/item_100.py b/example_code/item_100.py new file mode 100755 index 0000000..ed06714 --- /dev/null +++ b/example_code/item_100.py @@ -0,0 +1,172 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +numbers = [93, 86, 11, 68, 70] +numbers.sort() +print(numbers) + + +print("Example 2") +class Tool: + def __init__(self, name, weight): + self.name = name + self.weight = weight + + def __repr__(self): + return f"Tool({self.name!r}, {self.weight})" + +tools = [ + Tool("level", 3.5), + Tool("hammer", 1.25), + Tool("screwdriver", 0.5), + Tool("chisel", 0.25), +] + + +print("Example 3") +try: + tools.sort() +except: + logging.exception('Expected') +else: + assert False + + +print("Example 4") +print("Unsorted:", repr(tools)) +tools.sort(key=lambda x: x.name) +print("\nSorted: ", tools) + + +print("Example 5") +tools.sort(key=lambda x: x.weight) +print("By weight:", tools) + + +print("Example 6") +places = ["home", "work", "New York", "Paris"] +places.sort() +print("Case sensitive: ", places) +places.sort(key=lambda x: x.lower()) +print("Case insensitive:", places) + + +print("Example 7") +power_tools = [ + Tool("drill", 4), + Tool("circular saw", 5), + Tool("jackhammer", 40), + Tool("sander", 4), +] + + +print("Example 8") +saw = (5, "circular saw") +jackhammer = (40, "jackhammer") +assert not (jackhammer < saw) # Matches expectations + + +print("Example 9") +drill = (4, "drill") +sander = (4, "sander") +assert drill[0] == sander[0] # Same weight +assert drill[1] < sander[1] # Alphabetically less +assert drill < sander # Thus, drill comes first + + +print("Example 10") +power_tools.sort(key=lambda x: (x.weight, x.name)) +print(power_tools) + + +print("Example 11") +power_tools.sort( + key=lambda x: (x.weight, x.name), + reverse=True, # Makes all criteria descending +) +print(power_tools) + + +print("Example 12") +power_tools.sort(key=lambda x: (-x.weight, x.name)) +print(power_tools) + + +print("Example 13") +try: + power_tools.sort(key=lambda x: (x.weight, -x.name), reverse=True) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 14") +power_tools.sort( + key=lambda x: x.name, # Name ascending +) +power_tools.sort( + key=lambda x: x.weight, # Weight descending + reverse=True, +) +print(power_tools) + + +print("Example 15") +power_tools.sort(key=lambda x: x.name) +print(power_tools) + + +print("Example 16") +power_tools.sort( + key=lambda x: x.weight, + reverse=True, +) +print(power_tools) diff --git a/example_code/item_101.py b/example_code/item_101.py new file mode 100755 index 0000000..42bd7fd --- /dev/null +++ b/example_code/item_101.py @@ -0,0 +1,76 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +butterflies = ["Swallowtail", "Monarch", "Red Admiral"] +print(f"Before {butterflies}") +butterflies.sort() +print(f"After {butterflies}") + + +print("Example 2") +original = ["Swallowtail", "Monarch", "Red Admiral"] +alphabetical = sorted(original) +print(f"Original {original}") +print(f"Sorted {alphabetical}") + + +print("Example 3") +patterns = {"solid", "spotted", "cells"} +print(sorted(patterns)) + + +print("Example 4") +legs = {"insects": 6, "spiders": 8, "lizards": 4} +sorted_legs = sorted( + legs, + key=lambda x: legs[x], + reverse=True, +) +print(sorted_legs) diff --git a/example_code/item_102.py b/example_code/item_102.py new file mode 100755 index 0000000..b546c52 --- /dev/null +++ b/example_code/item_102.py @@ -0,0 +1,123 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +data = list(range(10**5)) +index = data.index(91234) +assert index == 91234 + + +print("Example 2") +def find_closest(sequence, goal): + for index, value in enumerate(sequence): + if goal < value: + return index + raise ValueError(f"{goal} is out of bounds") + +index = find_closest(data, 91234.56) +assert index == 91235 + +try: + find_closest(data, 100000000) +except ValueError: + pass # Expected +else: + assert False + + +print("Example 3") +from bisect import bisect_left + +index = bisect_left(data, 91234) # Exact match +assert index == 91234 + +index = bisect_left(data, 91234.56) # Closest match +assert index == 91235 + + +print("Example 4") +import random +import timeit + +size = 10**5 +iterations = 1000 + +data = list(range(size)) +to_lookup = [random.randint(0, size) for _ in range(iterations)] + +def run_linear(data, to_lookup): + for index in to_lookup: + data.index(index) + +def run_bisect(data, to_lookup): + for index in to_lookup: + bisect_left(data, index) + +baseline = ( + timeit.timeit( + stmt="run_linear(data, to_lookup)", + globals=globals(), + number=10, + ) + / 10 +) +print(f"Linear search takes {baseline:.6f}s") + +comparison = ( + timeit.timeit( + stmt="run_bisect(data, to_lookup)", + globals=globals(), + number=10, + ) + / 10 +) +print(f"Bisect search takes {comparison:.6f}s") + +slowdown = 1 + ((baseline - comparison) / comparison) +print(f"{slowdown:.1f}x slower") diff --git a/example_code/item_103.py b/example_code/item_103.py new file mode 100755 index 0000000..e4a691a --- /dev/null +++ b/example_code/item_103.py @@ -0,0 +1,243 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class Email: + def __init__(self, sender, receiver, message): + self.sender = sender + self.receiver = receiver + self.message = message + + +print("Example 2") +def get_emails(): + yield Email("foo@example.com", "bar@example.com", "hello1") + yield Email("baz@example.com", "banana@example.com", "hello2") + yield None + yield Email("meep@example.com", "butter@example.com", "hello3") + yield Email("stuff@example.com", "avocado@example.com", "hello4") + yield None + yield Email("thingy@example.com", "orange@example.com", "hello5") + yield Email("roger@example.com", "bob@example.com", "hello6") + yield None + yield Email("peanut@example.com", "alice@example.com", "hello7") + yield None + +EMAIL_IT = get_emails() + +class NoEmailError(Exception): + pass + +def try_receive_email(): + # Returns an Email instance or raises NoEmailError + try: + email = next(EMAIL_IT) + except StopIteration: + email = None + + if not email: + raise NoEmailError + + print(f"Produced email: {email.message}") + return email + + +print("Example 3") +def produce_emails(queue): + while True: + try: + email = try_receive_email() + except NoEmailError: + return + else: + queue.append(email) # Producer + + +print("Example 4") +def consume_one_email(queue): + if not queue: + return + email = queue.pop(0) # Consumer + # Index the message for long-term archival + print(f"Consumed email: {email.message}") + + +print("Example 5") +def loop(queue, keep_running): + while keep_running(): + produce_emails(queue) + consume_one_email(queue) + + +def make_test_end(): + count = list(range(10)) + + def func(): + if count: + count.pop() + return True + return False + + return func + + +def my_end_func(): + pass + +my_end_func = make_test_end() +loop([], my_end_func) + + +print("Example 6") +import timeit + +def list_append_benchmark(count): + def run(queue): + for i in range(count): + queue.append(i) + + return timeit.timeit( + setup="queue = []", + stmt="run(queue)", + globals=locals(), + number=1, + ) + + +print("Example 7") +for i in range(1, 6): + count = i * 1_000_000 + delay = list_append_benchmark(count) + print(f"Count {count:>5,} takes: {delay*1e3:>6.2f}ms") + + +print("Example 8") +def list_pop_benchmark(count): + def prepare(): + return list(range(count)) + + def run(queue): + while queue: + queue.pop(0) + + return timeit.timeit( + setup="queue = prepare()", + stmt="run(queue)", + globals=locals(), + number=1, + ) + + +print("Example 9") +for i in range(1, 6): + count = i * 10_000 + delay = list_pop_benchmark(count) + print(f"Count {count:>5,} takes: {delay*1e3:>6.2f}ms") + + +print("Example 10") +import collections + +def consume_one_email(queue): + if not queue: + return + email = queue.popleft() # Consumer + # Process the email message + print(f"Consumed email: {email.message}") + +def my_end_func(): + pass + +my_end_func = make_test_end() +EMAIL_IT = get_emails() +loop(collections.deque(), my_end_func) + + +print("Example 11") +def deque_append_benchmark(count): + def prepare(): + return collections.deque() + + def run(queue): + for i in range(count): + queue.append(i) + + return timeit.timeit( + setup="queue = prepare()", + stmt="run(queue)", + globals=locals(), + number=1, + ) + +for i in range(1, 6): + count = i * 100_000 + delay = deque_append_benchmark(count) + print(f"Count {count:>5,} takes: {delay*1e3:>6.2f}ms") + + +print("Example 12") +def dequeue_popleft_benchmark(count): + def prepare(): + return collections.deque(range(count)) + + def run(queue): + while queue: + queue.popleft() + + return timeit.timeit( + setup="queue = prepare()", + stmt="run(queue)", + globals=locals(), + number=1, + ) + +for i in range(1, 6): + count = i * 100_000 + delay = dequeue_popleft_benchmark(count) + print(f"Count {count:>5,} takes: {delay*1e3:>6.2f}ms") diff --git a/example_code/item_104.py b/example_code/item_104.py new file mode 100755 index 0000000..9073442 --- /dev/null +++ b/example_code/item_104.py @@ -0,0 +1,366 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class Book: + def __init__(self, title, due_date): + self.title = title + self.due_date = due_date + + +print("Example 2") +def add_book(queue, book): + queue.append(book) + queue.sort(key=lambda x: x.due_date, reverse=True) + +queue = [] +add_book(queue, Book("Don Quixote", "2019-06-07")) +add_book(queue, Book("Frankenstein", "2019-06-05")) +add_book(queue, Book("Les Misérables", "2019-06-08")) +add_book(queue, Book("War and Peace", "2019-06-03")) + + +print("Example 3") +class NoOverdueBooks(Exception): + pass + +def next_overdue_book(queue, now): + if queue: + book = queue[-1] + if book.due_date < now: + queue.pop() + return book + + raise NoOverdueBooks + + +print("Example 4") +now = "2019-06-10" + +found = next_overdue_book(queue, now) +print(found.due_date, found.title) + +found = next_overdue_book(queue, now) +print(found.due_date, found.title) + + +print("Example 5") +def return_book(queue, book): + queue.remove(book) + +queue = [] +book = Book("Treasure Island", "2019-06-04") + +add_book(queue, book) +print("Before return:", [x.title for x in queue]) + +return_book(queue, book) +print("After return: ", [x.title for x in queue]) + + +print("Example 6") +try: + next_overdue_book(queue, now) +except NoOverdueBooks: + pass # Expected +else: + assert False # Doesn't happen + + +print("Example 7") +import random +import timeit + +def list_overdue_benchmark(count): + def prepare(): + to_add = list(range(count)) + random.shuffle(to_add) + return [], to_add + + def run(queue, to_add): + for i in to_add: + queue.append(i) + queue.sort(reverse=True) + + while queue: + queue.pop() + + return timeit.timeit( + setup="queue, to_add = prepare()", + stmt=f"run(queue, to_add)", + globals=locals(), + number=1, + ) + + +print("Example 8") +for i in range(1, 6): + count = i * 1_000 + delay = list_overdue_benchmark(count) + print(f"Count {count:>5,} takes: {delay*1e3:>6.2f}ms") + + +print("Example 9") +def list_return_benchmark(count): + def prepare(): + queue = list(range(count)) + random.shuffle(queue) + + to_return = list(range(count)) + random.shuffle(to_return) + + return queue, to_return + + def run(queue, to_return): + for i in to_return: + queue.remove(i) + + return timeit.timeit( + setup="queue, to_return = prepare()", + stmt=f"run(queue, to_return)", + globals=locals(), + number=1, + ) + + +print("Example 10") +for i in range(1, 6): + count = i * 1_000 + delay = list_return_benchmark(count) + print(f"Count {count:>5,} takes: {delay*1e3:>6.2f}ms") + + +print("Example 11") +from heapq import heappush + +def add_book(queue, book): + heappush(queue, book) + + +print("Example 12") +try: + queue = [] + add_book(queue, Book("Little Women", "2019-06-05")) + add_book(queue, Book("The Time Machine", "2019-05-30")) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 13") +import functools + +@functools.total_ordering +class Book: + def __init__(self, title, due_date): + self.title = title + self.due_date = due_date + + def __lt__(self, other): + return self.due_date < other.due_date + + +print("Example 14") +queue = [] +add_book(queue, Book("Pride and Prejudice", "2019-06-01")) +add_book(queue, Book("The Time Machine", "2019-05-30")) +add_book(queue, Book("Crime and Punishment", "2019-06-06")) +add_book(queue, Book("Wuthering Heights", "2019-06-12")) +print([b.title for b in queue]) + + +print("Example 15") +queue = [ + Book("Pride and Prejudice", "2019-06-01"), + Book("The Time Machine", "2019-05-30"), + Book("Crime and Punishment", "2019-06-06"), + Book("Wuthering Heights", "2019-06-12"), +] +queue.sort() +print([b.title for b in queue]) + + +print("Example 16") +from heapq import heapify + +queue = [ + Book("Pride and Prejudice", "2019-06-01"), + Book("The Time Machine", "2019-05-30"), + Book("Crime and Punishment", "2019-06-06"), + Book("Wuthering Heights", "2019-06-12"), +] +heapify(queue) +print([b.title for b in queue]) + + +print("Example 17") +from heapq import heappop + +def next_overdue_book(queue, now): + if queue: + book = queue[0] # Most overdue first + if book.due_date < now: + heappop(queue) # Remove the overdue book + return book + + raise NoOverdueBooks + + +print("Example 18") +now = "2019-06-02" + +book = next_overdue_book(queue, now) +print(book.due_date, book.title) + +book = next_overdue_book(queue, now) +print(book.due_date, book.title) + +try: + next_overdue_book(queue, now) +except NoOverdueBooks: + pass # Expected +else: + assert False # Doesn't happen + + +print("Example 19") +def heap_overdue_benchmark(count): + def prepare(): + to_add = list(range(count)) + random.shuffle(to_add) + return [], to_add + + def run(queue, to_add): + for i in to_add: + heappush(queue, i) + while queue: + heappop(queue) + + return timeit.timeit( + setup="queue, to_add = prepare()", + stmt=f"run(queue, to_add)", + globals=locals(), + number=1, + ) + + +print("Example 20") +for i in range(1, 6): + count = i * 10_000 + delay = heap_overdue_benchmark(count) + print(f"Count {count:>5,} takes: {delay*1e3:>6.2f}ms") + + +print("Example 21") +@functools.total_ordering +class Book: + def __init__(self, title, due_date): + self.title = title + self.due_date = due_date + self.returned = False # New field + + def __lt__(self, other): + return self.due_date < other.due_date + + +print("Example 22") +def next_overdue_book(queue, now): + while queue: + book = queue[0] + if book.returned: + heappop(queue) + continue + + if book.due_date < now: + heappop(queue) + return book + + break + + raise NoOverdueBooks + + +queue = [] + +book = Book("Pride and Prejudice", "2019-06-01") +add_book(queue, book) + +book = Book("The Time Machine", "2019-05-30") +add_book(queue, book) +book.returned = True + +book = Book("Crime and Punishment", "2019-06-06") +add_book(queue, book) +book.returned = True + +book = Book("Wuthering Heights", "2019-06-12") +add_book(queue, book) + +now = "2019-06-11" + +book = next_overdue_book(queue, now) +assert book.title == "Pride and Prejudice" + +try: + next_overdue_book(queue, now) +except NoOverdueBooks: + pass # Expected +else: + assert False # Doesn't happen + + +print("Example 23") +def return_book(queue, book): + book.returned = True + + +assert not book.returned +return_book(queue, book) +assert book.returned diff --git a/example_code/item_105.py b/example_code/item_105.py new file mode 100755 index 0000000..50d3523 --- /dev/null +++ b/example_code/item_105.py @@ -0,0 +1,122 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +import time + +now = 1710047865.0 +local_tuple = time.localtime(now) +time_format = "%Y-%m-%d %H:%M:%S" +time_str = time.strftime(time_format, local_tuple) +print(time_str) + + +print("Example 2") +time_tuple = time.strptime(time_str, time_format) +utc_now = time.mktime(time_tuple) +print(utc_now) + + +print("Example 3") +parse_format = "%Y-%m-%d %H:%M:%S %Z" +depart_sfo = "2024-03-09 21:17:45 PST" +time_tuple = time.strptime(depart_sfo, parse_format) +time_str = time.strftime(time_format, time_tuple) +print(time_str) + + +print("Example 4") +try: + arrival_nyc = "2024-03-10 03:31:18 EDT" + time_tuple = time.strptime(arrival_nyc, parse_format) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +from datetime import datetime, timezone + +now = datetime(2024, 3, 10, 5, 17, 45) +now_utc = now.replace(tzinfo=timezone.utc) +now_local = now_utc.astimezone() +print(now_local) + + +print("Example 6") +time_str = "2024-03-09 21:17:45" +now = datetime.strptime(time_str, time_format) +time_tuple = now.timetuple() +utc_now = time.mktime(time_tuple) +print(utc_now) + + +print("Example 7") +from zoneinfo import ZoneInfo + +arrival_nyc = "2024-03-10 03:31:18" +nyc_dt_naive = datetime.strptime(arrival_nyc, time_format) +eastern = ZoneInfo("US/Eastern") +nyc_dt = nyc_dt_naive.replace(tzinfo=eastern) +utc_dt = nyc_dt.astimezone(timezone.utc) +print("EDT:", nyc_dt) +print("UTC:", utc_dt) + + +print("Example 8") +pacific = ZoneInfo("US/Pacific") +sf_dt = utc_dt.astimezone(pacific) +print("PST:", sf_dt) + + +print("Example 9") +nepal = ZoneInfo("Asia/Katmandu") +nepal_dt = utc_dt.astimezone(nepal) +print("NPT", nepal_dt) diff --git a/example_code/item_106.py b/example_code/item_106.py new file mode 100755 index 0000000..5974021 --- /dev/null +++ b/example_code/item_106.py @@ -0,0 +1,100 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +rate = 1.45 +seconds = 3 * 60 + 42 +cost = rate * seconds / 60 +print(cost) + + +print("Example 2") +print(round(cost, 2)) + + +print("Example 3") +from decimal import Decimal + +rate = Decimal("1.45") +seconds = Decimal(3 * 60 + 42) +cost = rate * seconds / Decimal(60) +print(cost) + + +print("Example 4") +print(Decimal("1.45")) +print(Decimal(1.45)) + + +print("Example 5") +print("456") +print(456) + + +print("Example 6") +rate = Decimal("0.05") +seconds = Decimal("5") +small_cost = rate * seconds / Decimal(60) +print(small_cost) + + +print("Example 7") +print(round(small_cost, 2)) + + +print("Example 8") +from decimal import ROUND_UP + +rounded = cost.quantize(Decimal("0.01"), rounding=ROUND_UP) +print(f"Rounded {cost} to {rounded}") + + +print("Example 9") +rounded = small_cost.quantize(Decimal("0.01"), rounding=ROUND_UP) +print(f"Rounded {small_cost} to {rounded}") diff --git a/example_code/item_44.py b/example_code/item_107.py similarity index 61% rename from example_code/item_44.py rename to example_code/item_107.py index 3910cdc..05c9099 100755 --- a/example_code/item_44.py +++ b/example_code/item_107.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,88 +14,122 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment +### 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() -# Example 1 -class GameState(object): +atexit.register(close_open_files) +### End book environment setup + + +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 +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 -class GameState(object): +print("Example 5") +class GameState: def __init__(self): self.level = 0 self.lives = 4 - self.points = 0 + 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 -class GameState(object): +print("Example 9") +class GameState: def __init__(self, level=0, lives=4, points=0): self.level = level self.lives = lives 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) @@ -103,29 +137,30 @@ def unpickle_game_state(kwargs): print(state_after.__dict__) -# Example 14 -class GameState(object): +print("Example 14") +class GameState: def __init__(self, level=0, lives=4, points=0, magic=5): self.level = level self.lives = lives self.points = points - self.magic = magic + self.magic = magic # New field -# Example 15 +print("Example 15") +print("Before:", state.__dict__) state_after = pickle.loads(serialized) -print(state_after.__dict__) +print("After: ", state_after.__dict__) -# Example 16 -class GameState(object): +print("Example 16") +class GameState: def __init__(self, level=0, points=0, magic=5): self.level = level self.points = points self.magic = magic -# Example 17 +print("Example 17") try: pickle.loads(serialized) except: @@ -134,40 +169,42 @@ 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: - kwargs.pop('lives') + del kwargs["lives"] return GameState(**kwargs) -# Example 20 +print("Example 20") copyreg.pickle(GameState, pickle_game_state) +print("Before:", state.__dict__) state_after = pickle.loads(serialized) -print(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(object): + +class BetterGameState: def __init__(self, level=0, points=0, magic=5): self.level = level self.points = points self.magic = magic -# Example 22 +print("Example 22") try: pickle.loads(serialized) except: @@ -176,15 +213,15 @@ def __init__(self, level=0, points=0, magic=5): assert False -# Example 23 -print(serialized[:25]) +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[:35]) +print(serialized) diff --git a/example_code/item_40_example_18.py b/example_code/item_108/testing/assert_test.py similarity index 54% rename from example_code/item_40_example_18.py rename to example_code/item_108/testing/assert_test.py index 2f5c023..8ff62cb 100755 --- a/example_code/item_40_example_18.py +++ b/example_code/item_108/testing/assert_test.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,21 +14,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT +from unittest import TestCase, main +from utils import to_str +class AssertTestCase(TestCase): + def test_assert_helper(self): + expected = 12 + found = 2 * 5 + self.assertEqual(expected, found) -# Example 18 -def delegated(): - yield 1 - yield 2 + def test_assert_statement(self): + expected = 12 + found = 2 * 5 + assert expected == found -def composed(): - yield 'A' - for value in delegated(): # yield from in Python 3 - yield value - yield 'B' - -print list(composed()) +if __name__ == "__main__": + main() diff --git a/example_code/item_108/testing/data_driven_test.py b/example_code/item_108/testing/data_driven_test.py new file mode 100755 index 0000000..b08c614 --- /dev/null +++ b/example_code/item_108/testing/data_driven_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase, main +from utils import to_str + +class DataDrivenTestCase(TestCase): + def test_good(self): + good_cases = [ + (b"my bytes", "my bytes"), + ("no error", b"no error"), # This one will fail + ("other str", "other str"), + ] + for value, expected in good_cases: + with self.subTest(value): + self.assertEqual(expected, to_str(value)) + + def test_bad(self): + bad_cases = [ + (object(), TypeError), + (b"\xfa\xfa", UnicodeDecodeError), + ] + for value, exception in bad_cases: + with self.subTest(value): + with self.assertRaises(exception): + to_str(value) + +if __name__ == "__main__": + main() diff --git a/example_code/item_108/testing/helper_test.py b/example_code/item_108/testing/helper_test.py new file mode 100755 index 0000000..c2d734a --- /dev/null +++ b/example_code/item_108/testing/helper_test.py @@ -0,0 +1,59 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase, main + +def sum_squares(values): + cumulative = 0 + for value in values: + cumulative += value**2 + yield cumulative + +class HelperTestCase(TestCase): + def verify_complex_case(self, values, expected): + expect_it = iter(expected) + found_it = iter(sum_squares(values)) + test_it = zip(expect_it, found_it, strict=True) + + for i, (expect, found) in enumerate(test_it): + if found != expect: + self.fail(f"Index {i} is wrong: {found} != {expect}") + + def test_too_short(self): + values = [1.1, 2.2] + expected = [1.1**2] + self.verify_complex_case(values, expected) + + def test_too_long(self): + values = [1.1, 2.2] + expected = [ + 1.1**2, + 1.1**2 + 2.2**2, + 0, # Value doesn't matter + ] + self.verify_complex_case(values, expected) + + def test_wrong_results(self): + values = [1.1, 2.2, 3.3] + expected = [ + 1.1**2, + 1.1**2 + 2.2**2, + 1.1**2 + 2.2**2 + 3.3**2 + 4.4**2, + ] + self.verify_complex_case(values, expected) + +if __name__ == "__main__": + main() diff --git a/example_code/item_56/testing/utils.py b/example_code/item_108/testing/utils.py similarity index 66% rename from example_code/item_56/testing/utils.py rename to example_code/item_108/testing/utils.py index 8403358..7d14a78 100755 --- a/example_code/item_56/testing/utils.py +++ b/example_code/item_108/testing/utils.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,18 +14,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 def to_str(data): if isinstance(data, str): return data elif isinstance(data, bytes): - return data.decode('utf-8') + 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_03_example_04.py b/example_code/item_108/testing/utils_error_test.py similarity index 54% rename from example_code/item_03_example_04.py rename to example_code/item_108/testing/utils_error_test.py index c165b2f..a47f9b8 100755 --- a/example_code/item_03_example_04.py +++ b/example_code/item_108/testing/utils_error_test.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,19 +14,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT +from unittest import TestCase, main +from utils import to_str +class UtilsErrorTestCase(TestCase): + def test_to_str_bad(self): + with self.assertRaises(TypeError): + to_str(object()) -# Example 4 -def to_str(unicode_or_str): - if isinstance(unicode_or_str, unicode): - value = unicode_or_str.encode('utf-8') - else: - value = unicode_or_str - return value # Instance of str + def test_to_str_bad_encoding(self): + with self.assertRaises(UnicodeDecodeError): + to_str(b"\xfa\xfa") -print(repr(to_str(u'foo'))) -print(repr(to_str('foo'))) +if __name__ == "__main__": + main() diff --git a/example_code/item_56/testing/utils_test.py b/example_code/item_108/testing/utils_test.py similarity index 63% rename from example_code/item_56/testing/utils_test.py rename to example_code/item_108/testing/utils_test.py index e38896c..56fe1a8 100755 --- a/example_code/item_56/testing/utils_test.py +++ b/example_code/item_108/testing/utils_test.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,25 +14,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 2 from unittest import TestCase, main from utils import to_str class UtilsTestCase(TestCase): def test_to_str_bytes(self): - self.assertEqual('hello', to_str(b'hello')) + 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_to_str_bad(self): - self.assertRaises(TypeError, to_str, object()) + def test_failing(self): + 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_11.py b/example_code/item_11.py deleted file mode 100755 index 09cffa5..0000000 --- a/example_code/item_11.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -names = ['Cecilia', 'Lise', 'Marie'] -letters = [len(n) for n in names] -print(letters) - - -# Example 2 -longest_name = None -max_letters = 0 - -for i in range(len(names)): - count = letters[i] - if count > max_letters: - longest_name = names[i] - max_letters = count - -print(longest_name) - - -# Example 3 -longest_name = None -max_letters = 0 -for i, name in enumerate(names): - count = letters[i] - if count > max_letters: - longest_name = name - max_letters = count -print(longest_name) - - -# Example 4 -longest_name = None -max_letters = 0 -for name, count in zip(names, letters): - if count > max_letters: - longest_name = name - max_letters = count -print(longest_name) - - -# Example 5 -names.append('Rosalind') -for name, count in zip(names, letters): - print(name) diff --git a/example_code/item_56.py b/example_code/item_110/testing/environment_test.py similarity index 63% rename from example_code/item_56.py rename to example_code/item_110/testing/environment_test.py index 05ffbc7..500c6f2 100755 --- a/example_code/item_56.py +++ b/example_code/item_110/testing/environment_test.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,18 +14,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 3 +from pathlib import Path from tempfile import TemporaryDirectory -from unittest import TestCase -class MyTest(TestCase): +from unittest import TestCase, main + +class EnvironmentTest(TestCase): def setUp(self): self.test_dir = TemporaryDirectory() + self.test_path = Path(self.test_dir.name) + def tearDown(self): self.test_dir.cleanup() - # Test methods follow + + def test_modify_file(self): + with open(self.test_path / "data.bin", "w") as f: + f.write("hello") + +if __name__ == "__main__": + main() diff --git a/example_code/item_110/testing/integration_test.py b/example_code/item_110/testing/integration_test.py new file mode 100755 index 0000000..dbc614b --- /dev/null +++ b/example_code/item_110/testing/integration_test.py @@ -0,0 +1,39 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase, main + +def setUpModule(): + print("* Module setup") + +def tearDownModule(): + print("* Module clean-up") + +class IntegrationTest(TestCase): + def setUp(self): + print("* Test setup") + + def tearDown(self): + print("* Test clean-up") + + def test_end_to_end1(self): + print("* Test 1") + + def test_end_to_end2(self): + print("* Test 2") + +if __name__ == "__main__": + main() diff --git a/example_code/item_111.py b/example_code/item_111.py new file mode 100755 index 0000000..2ce0c85 --- /dev/null +++ b/example_code/item_111.py @@ -0,0 +1,323 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class DatabaseConnection: + def __init__(self, host, port): + pass + + +class DatabaseConnectionError(Exception): + pass + +def get_animals(database, species): + # Query the Database + raise DatabaseConnectionError("Not connected") + # Return a list of (name, last_mealtime) tuples + + +print("Example 2") +try: + database = DatabaseConnection("localhost", "4444") + + get_animals(database, "Meerkat") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +from datetime import datetime +from unittest.mock import Mock + +mock = Mock(spec=get_animals) +expected = [ + ("Spot", datetime(2024, 6, 5, 11, 15)), + ("Fluffy", datetime(2024, 6, 5, 12, 30)), + ("Jojo", datetime(2024, 6, 5, 12, 45)), +] +mock.return_value = expected + + +print("Example 4") +try: + mock.does_not_exist +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +database = object() +result = mock(database, "Meerkat") +assert result == expected + + +print("Example 6") +mock.assert_called_once_with(database, "Meerkat") + + +print("Example 7") +try: + mock.assert_called_once_with(database, "Giraffe") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +from unittest.mock import ANY + +mock = Mock(spec=get_animals) +mock("database 1", "Rabbit") +mock("database 2", "Bison") +mock("database 3", "Meerkat") + +mock.assert_called_with(ANY, "Meerkat") + + +print("Example 9") +try: + class MyError(Exception): + pass + + mock = Mock(spec=get_animals) + mock.side_effect = MyError("Whoops! Big problem") + result = mock(database, "Meerkat") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 10") +def get_food_period(database, species): + # Query the Database + pass + # Return a time delta + +def feed_animal(database, name, when): + # Write to the Database + pass + +def do_rounds(database, species): + now = datetime.now() + feeding_timedelta = get_food_period(database, species) + animals = get_animals(database, species) + fed = 0 + + for name, last_mealtime in animals: + if (now - last_mealtime) > feeding_timedelta: + feed_animal(database, name, now) + fed += 1 + + return fed + + +print("Example 11") +def do_rounds( + database, + species, + *, + now_func=datetime.now, + food_func=get_food_period, + animals_func=get_animals, + feed_func=feed_animal +): + now = now_func() + feeding_timedelta = food_func(database, species) + animals = animals_func(database, species) + fed = 0 + + for name, last_mealtime in animals: + if (now - last_mealtime) > feeding_timedelta: + feed_func(database, name, now) + fed += 1 + + return fed + + +print("Example 12") +from datetime import timedelta + +now_func = Mock(spec=datetime.now) +now_func.return_value = datetime(2024, 6, 5, 15, 45) + +food_func = Mock(spec=get_food_period) +food_func.return_value = timedelta(hours=3) + +animals_func = Mock(spec=get_animals) +animals_func.return_value = [ + ("Spot", datetime(2024, 6, 5, 11, 15)), + ("Fluffy", datetime(2024, 6, 5, 12, 30)), + ("Jojo", datetime(2024, 6, 5, 12, 45)), +] + +feed_func = Mock(spec=feed_animal) + + +print("Example 13") +result = do_rounds( + database, + "Meerkat", + now_func=now_func, + food_func=food_func, + animals_func=animals_func, + feed_func=feed_func, +) + +assert result == 2 + + +print("Example 14") +from unittest.mock import call + +food_func.assert_called_once_with(database, "Meerkat") + +animals_func.assert_called_once_with(database, "Meerkat") + +feed_func.assert_has_calls( + [ + call(database, "Spot", now_func.return_value), + call(database, "Fluffy", now_func.return_value), + ], + any_order=True, +) + +# Make sure these variables don't pollute later tests +del food_func +del animals_func +del feed_func + + +print("Example 15") +from unittest.mock import patch + +print("Outside patch:", get_animals) + +with patch("__main__.get_animals"): + print("Inside patch: ", get_animals) + +print("Outside again:", get_animals) + + +print("Example 16") +try: + fake_now = datetime(2024, 6, 5, 15, 45) + + with patch("datetime.datetime.now"): + datetime.now.return_value = fake_now +except: + logging.exception('Expected') +else: + assert False + + +print("Example 17") +def get_do_rounds_time(): + return datetime.now() + +def do_rounds(database, species): + now = get_do_rounds_time() + +with patch("__main__.get_do_rounds_time"): + pass + + +print("Example 18") +def do_rounds(database, species, *, now_func=datetime.now): + now = now_func() + feeding_timedelta = get_food_period(database, species) + animals = get_animals(database, species) + fed = 0 + + for name, last_mealtime in animals: + if (now - last_mealtime) > feeding_timedelta: + feed_animal(database, name, now) + fed += 1 + + return fed + + +print("Example 19") +from unittest.mock import DEFAULT + +with patch.multiple( + "__main__", + autospec=True, + get_food_period=DEFAULT, + get_animals=DEFAULT, + feed_animal=DEFAULT, +): + now_func = Mock(spec=datetime.now) + now_func.return_value = datetime(2024, 6, 5, 15, 45) + get_food_period.return_value = timedelta(hours=3) + get_animals.return_value = [ + ("Spot", datetime(2024, 6, 5, 11, 15)), + ("Fluffy", datetime(2024, 6, 5, 12, 30)), + ("Jojo", datetime(2024, 6, 5, 12, 45)), + ] + + + print("Example 20") + result = do_rounds(database, "Meerkat", now_func=now_func) + assert result == 2 + + get_food_period.assert_called_once_with(database, "Meerkat") + get_animals.assert_called_once_with(database, "Meerkat") + feed_animal.assert_has_calls( + [ + call(database, "Spot", now_func.return_value), + call(database, "Fluffy", now_func.return_value), + ], + any_order=True, + ) diff --git a/example_code/item_112.py b/example_code/item_112.py new file mode 100755 index 0000000..c707d69 --- /dev/null +++ b/example_code/item_112.py @@ -0,0 +1,168 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +class ZooDatabase: + + def get_animals(self, species): + pass + + def get_food_period(self, species): + pass + + def feed_animal(self, name, when): + pass + + +print("Example 2") +from datetime import datetime + +def do_rounds(database, species, *, now_func=datetime.now): + now = now_func() + feeding_timedelta = database.get_food_period(species) + animals = database.get_animals(species) + fed = 0 + + for name, last_mealtime in animals: + if (now - last_mealtime) >= feeding_timedelta: + database.feed_animal(name, now) + fed += 1 + + return fed + + +print("Example 3") +from unittest.mock import Mock + +database = Mock(spec=ZooDatabase) +print(database.feed_animal) +database.feed_animal() +database.feed_animal.assert_any_call() + + +print("Example 4") +from datetime import timedelta +from unittest.mock import call + +now_func = Mock(spec=datetime.now) +now_func.return_value = datetime(2019, 6, 5, 15, 45) + +database = Mock(spec=ZooDatabase) +database.get_food_period.return_value = timedelta(hours=3) +database.get_animals.return_value = [ + ("Spot", datetime(2019, 6, 5, 11, 15)), + ("Fluffy", datetime(2019, 6, 5, 12, 30)), + ("Jojo", datetime(2019, 6, 5, 12, 55)), +] + + +print("Example 5") +result = do_rounds(database, "Meerkat", now_func=now_func) +assert result == 2 + +database.get_food_period.assert_called_once_with("Meerkat") +database.get_animals.assert_called_once_with("Meerkat") +database.feed_animal.assert_has_calls( + [ + call("Spot", now_func.return_value), + call("Fluffy", now_func.return_value), + ], + any_order=True, +) + + +print("Example 6") +try: + database.bad_method_name() +except: + logging.exception('Expected') +else: + assert False + + +print("Example 7") +DATABASE = None + +def get_database(): + global DATABASE + if DATABASE is None: + DATABASE = ZooDatabase() + return DATABASE + +def main(argv): + database = get_database() + species = argv[1] + count = do_rounds(database, species) + print(f"Fed {count} {species}(s)") + return 0 + + +print("Example 8") +import contextlib +import io +from unittest.mock import patch + +with patch("__main__.DATABASE", spec=ZooDatabase): + now = datetime.now() + + DATABASE.get_food_period.return_value = timedelta(hours=3) + DATABASE.get_animals.return_value = [ + ("Spot", now - timedelta(minutes=4.5)), + ("Fluffy", now - timedelta(hours=3.25)), + ("Jojo", now - timedelta(hours=3)), + ] + + fake_stdout = io.StringIO() + with contextlib.redirect_stdout(fake_stdout): + main(["program name", "Meerkat"]) + + found = fake_stdout.getvalue() + expected = "Fed 2 Meerkat(s)\n" + + assert found == expected diff --git a/example_code/item_113.py b/example_code/item_113.py new file mode 100755 index 0000000..e6f4bd1 --- /dev/null +++ b/example_code/item_113.py @@ -0,0 +1,97 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + import unittest + + class MyTestCase(unittest.TestCase): + def test_equal(self): + n = 5 + d = 3 + self.assertEqual(1.667, n / d) # Raises + + suite = unittest.defaultTestLoader.loadTestsFromTestCase(MyTestCase) + suite.debug() +except: + logging.exception('Expected') +else: + assert False + + +print("Example 2") +print(5 / 3 * 0.1) +print(0.1 * 5 / 3) + + +print("Example 3") +class MyTestCase2(unittest.TestCase): + def test_equal(self): + n = 5 + d = 3 + self.assertAlmostEqual(1.667, n / d, places=2) # Changed + +suite = unittest.defaultTestLoader.loadTestsFromTestCase(MyTestCase2) +unittest.TextTestRunner(stream=STDOUT).run(suite) + + +print("Example 4") +print(1e24 / 1.1e16) +print(1e24 / 1.101e16) + + +print("Example 5") +class MyTestCase3(unittest.TestCase): + def test_equal(self): + a = 1e24 / 1.1e16 + b = 1e24 / 1.101e16 + self.assertAlmostEqual(90.9e6, a, delta=0.1e6) + self.assertAlmostEqual(90.9e6, b, delta=0.1e6) +suite = unittest.defaultTestLoader.loadTestsFromTestCase(MyTestCase3) +unittest.TextTestRunner(stream=STDOUT).run(suite) diff --git a/example_code/item_114/debugging/always_breakpoint.py b/example_code/item_114/debugging/always_breakpoint.py new file mode 100755 index 0000000..6649805 --- /dev/null +++ b/example_code/item_114/debugging/always_breakpoint.py @@ -0,0 +1,36 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math + +def compute_rmse(observed, ideal): + total_err_2 = 0 + count = 0 + for got, wanted in zip(observed, ideal): + err_2 = (got - wanted) ** 2 + breakpoint() # Start the debugger here + total_err_2 += err_2 + count += 1 + + mean_err = total_err_2 / count + rmse = math.sqrt(mean_err) + return rmse + +result = compute_rmse( + [1.8, 1.7, 3.2, 6], + [2, 1.5, 3, 5], +) +print(result) diff --git a/example_code/item_114/debugging/conditional_breakpoint.py b/example_code/item_114/debugging/conditional_breakpoint.py new file mode 100755 index 0000000..3a31f38 --- /dev/null +++ b/example_code/item_114/debugging/conditional_breakpoint.py @@ -0,0 +1,36 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math + +def compute_rmse(observed, ideal): + total_err_2 = 0 + count = 0 + for got, wanted in zip(observed, ideal): + err_2 = (got - wanted) ** 2 + if err_2 >= 1: # Start the debugger if True + breakpoint() + total_err_2 += err_2 + count += 1 + mean_err = total_err_2 / count + rmse = math.sqrt(mean_err) + return rmse + +result = compute_rmse( + [1.8, 1.7, 3.2, 7], + [2, 1.5, 3, 5], +) +print(result) diff --git a/example_code/item_114/debugging/my_module.py b/example_code/item_114/debugging/my_module.py new file mode 100755 index 0000000..dd55060 --- /dev/null +++ b/example_code/item_114/debugging/my_module.py @@ -0,0 +1,34 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math + + +def squared_error(point, mean): + err = point - mean + return err**2 + + +def compute_variance(data): + mean = sum(data) / len(data) + err_2_sum = sum(squared_error(x, mean) for x in data) + variance = err_2_sum / (len(data) - 1) + return variance + + +def compute_stddev(data): + variance = compute_variance(data) + return math.sqrt(variance) diff --git a/example_code/item_33_example_02.py b/example_code/item_114/debugging/postmortem_breakpoint.py similarity index 52% rename from example_code/item_33_example_02.py rename to example_code/item_114/debugging/postmortem_breakpoint.py index 1ff6adf..ab0f35e 100755 --- a/example_code/item_33_example_02.py +++ b/example_code/item_114/debugging/postmortem_breakpoint.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,21 +14,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT +import math +def compute_rmse(observed, ideal): + total_err_2 = 0 + count = 0 + for got, wanted in zip(observed, ideal): + err_2 = (got - wanted) ** 2 + total_err_2 += err_2 + count += 1 -# Example 2 -class Meta(type): - def __new__(meta, name, bases, class_dict): - print(meta, name, bases, class_dict) - return type.__new__(meta, name, bases, class_dict) + mean_err = total_err_2 / count + rmse = math.sqrt(mean_err) + return rmse -class MyClassInPython2(object): - __metaclass__ = Meta - stuff = 123 - - def foo(self): - pass +result = compute_rmse( + [1.8, 1.7, 3.2, 7j], # Bad input + [2, 1.5, 3, 5], +) +print(result) diff --git a/example_code/item_59/tracemalloc/top_n.py b/example_code/item_115/tracemalloc/top_n.py similarity index 60% rename from example_code/item_59/tracemalloc/top_n.py rename to example_code/item_115/tracemalloc/top_n.py index 82a7b76..8a4abce 100755 --- a/example_code/item_59/tracemalloc/top_n.py +++ b/example_code/item_115/tracemalloc/top_n.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,21 +14,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 3 import tracemalloc -tracemalloc.start(10) # Save up to 10 stack frames -time1 = tracemalloc.take_snapshot() +tracemalloc.start(10) # Set stack depth +time1 = tracemalloc.take_snapshot() # Before snapshot + import waste_memory -x = waste_memory.run() -time2 = tracemalloc.take_snapshot() -stats = time2.compare_to(time1, 'lineno') +x = waste_memory.run() # Usage to debug +time2 = tracemalloc.take_snapshot() # After snapshot + +stats = time2.compare_to(time1, "lineno") # Compare snapshots for stat in stats[:3]: print(stat) diff --git a/example_code/item_59/tracemalloc/using_gc.py b/example_code/item_115/tracemalloc/using_gc.py similarity index 68% rename from example_code/item_59/tracemalloc/using_gc.py rename to example_code/item_115/tracemalloc/using_gc.py index a116265..39faa18 100755 --- a/example_code/item_59/tracemalloc/using_gc.py +++ b/example_code/item_115/tracemalloc/using_gc.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,20 +14,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 2 import gc + found_objects = gc.get_objects() -print('%d objects before' % len(found_objects)) +print("Before:", len(found_objects)) import waste_memory -x = waste_memory.run() + +hold_reference = waste_memory.run() + found_objects = gc.get_objects() -print('%d objects after' % len(found_objects)) +print("After: ", len(found_objects)) for obj in found_objects[:3]: print(repr(obj)[:100]) + +print("...") diff --git a/example_code/item_59/tracemalloc/waste_memory.py b/example_code/item_115/tracemalloc/waste_memory.py similarity index 71% rename from example_code/item_59/tracemalloc/waste_memory.py rename to example_code/item_115/tracemalloc/waste_memory.py index 4ea62e3..f545535 100755 --- a/example_code/item_59/tracemalloc/waste_memory.py +++ b/example_code/item_115/tracemalloc/waste_memory.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,20 +14,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 +# waste_memory.py import os -import hashlib -class MyObject(object): +class MyObject: def __init__(self): - self.x = os.urandom(100) - self.y = hashlib.sha1(self.x).hexdigest() + self.data = os.urandom(100) def get_data(): values = [] diff --git a/example_code/item_59/tracemalloc/with_trace.py b/example_code/item_115/tracemalloc/with_trace.py similarity index 71% rename from example_code/item_59/tracemalloc/with_trace.py rename to example_code/item_115/tracemalloc/with_trace.py index 1942ee6..3e8d7a2 100755 --- a/example_code/item_59/tracemalloc/with_trace.py +++ b/example_code/item_115/tracemalloc/with_trace.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,20 +14,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 4 import tracemalloc -tracemalloc.start(10) +tracemalloc.start(10) time1 = tracemalloc.take_snapshot() + import waste_memory + x = waste_memory.run() time2 = tracemalloc.take_snapshot() -stats = time2.compare_to(time1, 'traceback') + +stats = time2.compare_to(time1, "traceback") top = stats[0] -print('\n'.join(top.traceback.format())) +print("Biggest offender is:") +print("\n".join(top.traceback.format())) diff --git a/example_code/item_49.py b/example_code/item_118.py similarity index 59% rename from example_code/item_49.py rename to example_code/item_118.py index a66bad5..dd27484 100755 --- a/example_code/item_49.py +++ b/example_code/item_118.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,27 +14,54 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment +### 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() -# Example 1 +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") def palindrome(word): """Return True if the given word is a palindrome.""" return word == word[::-1] -assert palindrome('tacocat') -assert not palindrome('banana') +assert palindrome("tacocat") +assert not palindrome("banana") -# Example 2 -print(repr(palindrome.__doc__)) +print("Example 2") +print(palindrome.__doc__) -# Example 3 -"""Library for testing words for various linguistic patterns. +print("Example 3") +"""Library for finding linguistic patterns in words. Testing how words relate to each other can be tricky sometimes! This module provides easy ways to determine when words you've @@ -47,8 +74,8 @@ def palindrome(word): """ -# Example 4 -class Player(object): +print("Example 4") +class Player: """Represents a player of the game. Subclasses may override the 'tick' method to provide @@ -61,28 +88,28 @@ class Player(object): """ -# Example 5 +print("Example 5") import itertools + def find_anagrams(word, dictionary): """Find all anagrams for a word. This function only runs as fast as the test for - membership in the 'dictionary' container. It will - be slow if the dictionary is a list and fast if - it's a set. + membership in the 'dictionary' container. Args: word: String of the target word. - dictionary: Container with all strings that - are known to be actual words. + dictionary: collections.abc.Container with all + strings that are known to be actual words. Returns: List of anagrams that were found. Empty if none were found. """ permutations = itertools.permutations(word, len(word)) - possible = (''.join(x) for x in permutations) + 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_118_example_06.py b/example_code/item_118_example_06.py new file mode 100755 index 0000000..a1b101a --- /dev/null +++ b/example_code/item_118_example_06.py @@ -0,0 +1,25 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 6") +# Check types in this file with: python3 -m mypy + +from collections.abc import Container + +def find_anagrams(word: str, dictionary: Container[str]) -> list[str]: + return [] diff --git a/example_code/item_118_example_07.py b/example_code/item_118_example_07.py new file mode 100755 index 0000000..cfe7598 --- /dev/null +++ b/example_code/item_118_example_07.py @@ -0,0 +1,37 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 7") +# Check types in this file with: python3 -m mypy + +from collections.abc import Container + +def find_anagrams(word: str, dictionary: Container[str]) -> list[str]: + """Find all anagrams for a word. + + This function only runs as fast as the test for + membership in the 'dictionary' container. + + Args: + word: Target word. + dictionary: All known actual words. + + Returns: + Anagrams that were found. + """ + return [] diff --git a/example_code/item_50/api_package/api_consumer.py b/example_code/item_119/api_package/api_consumer.py similarity index 81% rename from example_code/item_50/api_package/api_consumer.py rename to example_code/item_119/api_package/api_consumer.py index 6fd5283..da151bc 100755 --- a/example_code/item_50/api_package/api_consumer.py +++ b/example_code/item_119/api_package/api_consumer.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 12 from mypackage import * a = Projectile(1.5, 3) @@ -29,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_50/api_package/main.py b/example_code/item_119/api_package/main2.py similarity index 73% rename from example_code/item_50/api_package/main.py rename to example_code/item_119/api_package/main2.py index b5087b3..993afef 100755 --- a/example_code/item_50/api_package/main.py +++ b/example_code/item_119/api_package/main2.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,11 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 from mypackage import utils diff --git a/example_code/item_119/api_package/mypackage/__init__.py b/example_code/item_119/api_package/mypackage/__init__.py new file mode 100755 index 0000000..54e681b --- /dev/null +++ b/example_code/item_119/api_package/mypackage/__init__.py @@ -0,0 +1,23 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__all__ = [] +from .models import * + +__all__ += models.__all__ +from .utils import * + +__all__ += utils.__all__ diff --git a/example_code/item_50/api_package/mypackage/models.py b/example_code/item_119/api_package/mypackage/models.py similarity index 71% rename from example_code/item_50/api_package/mypackage/models.py rename to example_code/item_119/api_package/mypackage/models.py index 1a58640..5b7b7ff 100755 --- a/example_code/item_50/api_package/mypackage/models.py +++ b/example_code/item_119/api_package/mypackage/models.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,16 +14,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT +__all__ = ["Projectile"] - -# Example 9 -__all__ = ['Projectile'] - -class Projectile(object): +class Projectile: def __init__(self, mass, velocity): self.mass = mass self.velocity = velocity diff --git a/example_code/item_50/api_package/mypackage/utils.py b/example_code/item_119/api_package/mypackage/utils.py similarity index 73% rename from example_code/item_50/api_package/mypackage/utils.py rename to example_code/item_119/api_package/mypackage/utils.py index 43a7cd7..327e0cd 100755 --- a/example_code/item_50/api_package/mypackage/utils.py +++ b/example_code/item_119/api_package/mypackage/utils.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,16 +14,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT +from .models import Projectile - -# Example 10 -from . models import Projectile - -__all__ = ['simulate_collision'] +__all__ = ["simulate_collision"] def _dot_product(a, b): pass diff --git a/example_code/item_50/namespace_package/frontend/__init__.py b/example_code/item_119/namespace_package/analysis/__init__.py similarity index 72% rename from example_code/item_50/namespace_package/frontend/__init__.py rename to example_code/item_119/namespace_package/analysis/__init__.py index a0d67b6..bb03669 100755 --- a/example_code/item_50/namespace_package/frontend/__init__.py +++ b/example_code/item_119/namespace_package/analysis/__init__.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,11 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 4 diff --git a/example_code/item_50/namespace_package/analysis/utils.py b/example_code/item_119/namespace_package/analysis/utils.py similarity index 76% rename from example_code/item_50/namespace_package/analysis/utils.py rename to example_code/item_119/namespace_package/analysis/utils.py index 058083f..a89126d 100755 --- a/example_code/item_50/namespace_package/analysis/utils.py +++ b/example_code/item_119/namespace_package/analysis/utils.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,16 +14,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT +import math -# Example 3 -import math def log_base2_bucket(value): return math.log(value, 2) + + + def inspect(value): pass diff --git a/example_code/item_50/namespace_package/analysis/__init__.py b/example_code/item_119/namespace_package/frontend/__init__.py similarity index 72% rename from example_code/item_50/namespace_package/analysis/__init__.py rename to example_code/item_119/namespace_package/frontend/__init__.py index 7302c16..bb03669 100755 --- a/example_code/item_50/namespace_package/analysis/__init__.py +++ b/example_code/item_119/namespace_package/frontend/__init__.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,11 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 2 diff --git a/example_code/item_50/namespace_package/frontend/utils.py b/example_code/item_119/namespace_package/frontend/utils.py similarity index 75% rename from example_code/item_50/namespace_package/frontend/utils.py rename to example_code/item_119/namespace_package/frontend/utils.py index 2cb6a65..0419e89 100755 --- a/example_code/item_50/namespace_package/frontend/utils.py +++ b/example_code/item_119/namespace_package/frontend/utils.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,15 +14,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 5 def stringify(value): return str(value) + + + def inspect(value): pass diff --git a/example_code/item_50/namespace_package/main.py b/example_code/item_119/namespace_package/main.py similarity index 77% rename from example_code/item_50/namespace_package/main.py rename to example_code/item_119/namespace_package/main.py index 37ce7ff..4963b4e 100755 --- a/example_code/item_50/namespace_package/main.py +++ b/example_code/item_119/namespace_package/main.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 6 from analysis.utils import log_base2_bucket from frontend.utils import stringify diff --git a/example_code/item_50/namespace_package/main2.py b/example_code/item_119/namespace_package/main2.py similarity index 73% rename from example_code/item_50/namespace_package/main2.py rename to example_code/item_119/namespace_package/main2.py index 9f10541..9071f42 100755 --- a/example_code/item_50/namespace_package/main2.py +++ b/example_code/item_119/namespace_package/main2.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,14 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 7 from analysis.utils import inspect from frontend.utils import inspect # Overwrites! -'frontend' in inspect.__module__ + +"frontend" in inspect.__module__ print(inspect.__module__) diff --git a/example_code/item_50/namespace_package/main3.py b/example_code/item_119/namespace_package/main3.py similarity index 75% rename from example_code/item_50/namespace_package/main3.py rename to example_code/item_119/namespace_package/main3.py index adb3810..41b2380 100755 --- a/example_code/item_50/namespace_package/main3.py +++ b/example_code/item_119/namespace_package/main3.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,16 +14,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 8 from analysis.utils import inspect as analysis_inspect from frontend.utils import inspect as frontend_inspect value = 33 if analysis_inspect(value) == frontend_inspect(value): - print('Inspection equal!') + print("Inspection equal!") diff --git a/example_code/item_119/namespace_package/main4.py b/example_code/item_119/namespace_package/main4.py new file mode 100755 index 0000000..99b16f8 --- /dev/null +++ b/example_code/item_119/namespace_package/main4.py @@ -0,0 +1,22 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import analysis.utils +import frontend.utils + +value = 33 +if analysis.utils.inspect(value) == frontend.utils.inspect(value): + print("Inspection equal!") diff --git a/example_code/item_12.py b/example_code/item_12.py deleted file mode 100755 index 0e7a262..0000000 --- a/example_code/item_12.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -for i in range(3): - print('Loop %d' % i) -else: - print('Else block!') - - -# Example 2 -for i in range(3): - print('Loop %d' % i) - if i == 1: - break -else: - print('Else block!') - - -# Example 3 -for x in []: - print('Never runs') -else: - print('For Else block!') - - -# Example 4 -while False: - print('Never runs') -else: - print('While Else block!') - - -# Example 5 -a = 4 -b = 9 - -for i in range(2, min(a, b) + 1): - print('Testing', i) - if a % i == 0 and b % i == 0: - print('Not coprime') - break -else: - print('Coprime') - - -# Example 6 -def coprime(a, b): - for i in range(2, min(a, b) + 1): - if a % i == 0 and b % i == 0: - return False - return True -print(coprime(4, 9)) -print(coprime(3, 6)) - - -# Example 7 -def coprime2(a, b): - is_coprime = True - for i in range(2, min(a, b) + 1): - if a % i == 0 and b % i == 0: - is_coprime = False - break - return is_coprime -print(coprime2(4, 9)) -print(coprime2(3, 6)) diff --git a/example_code/item_120.py b/example_code/item_120.py new file mode 100755 index 0000000..f4e6da6 --- /dev/null +++ b/example_code/item_120.py @@ -0,0 +1,63 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 4") +# db_connection.py +import sys + +class Win32Database: + pass + +class PosixDatabase: + pass + +if sys.platform.startswith("win32"): + Database = Win32Database +else: + Database = PosixDatabase diff --git a/example_code/item_54/module_scope/db_connection.py b/example_code/item_120/module_scope/db_connection.py similarity index 72% rename from example_code/item_54/module_scope/db_connection.py rename to example_code/item_120/module_scope/db_connection.py index 5c4a0c1..6db2ac5 100755 --- a/example_code/item_54/module_scope/db_connection.py +++ b/example_code/item_120/module_scope/db_connection.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,20 +14,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 3 # db_connection.py import __main__ -class TestingDatabase(object): +class TestingDatabase: pass -class RealDatabase(object): +class RealDatabase: pass if __main__.TESTING: diff --git a/example_code/item_54/module_scope/dev_main.py b/example_code/item_120/module_scope/dev_main.py similarity index 75% rename from example_code/item_54/module_scope/dev_main.py rename to example_code/item_120/module_scope/dev_main.py index 3e5a682..2044a89 100755 --- a/example_code/item_54/module_scope/dev_main.py +++ b/example_code/item_120/module_scope/dev_main.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 TESTING = True + import db_connection + db = db_connection.Database() diff --git a/example_code/item_54/module_scope/prod_main.py b/example_code/item_120/module_scope/prod_main.py similarity index 75% rename from example_code/item_54/module_scope/prod_main.py rename to example_code/item_120/module_scope/prod_main.py index 88924be..669078c 100755 --- a/example_code/item_54/module_scope/prod_main.py +++ b/example_code/item_120/module_scope/prod_main.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 2 TESTING = False + import db_connection + db = db_connection.Database() diff --git a/example_code/item_121.py b/example_code/item_121.py new file mode 100755 index 0000000..4d9f308 --- /dev/null +++ b/example_code/item_121.py @@ -0,0 +1,191 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +# my_module.py +def determine_weight(volume, density): + if density <= 0: + raise ValueError("Density must be positive") + + +try: + determine_weight(1, 0) +except ValueError: + pass +else: + assert False + + +print("Example 2") +# my_module.py +class Error(Exception): + """Base-class for all exceptions raised by this module.""" + +class InvalidDensityError(Error): + """There was a problem with a provided density value.""" + +class InvalidVolumeError(Error): + """There was a problem with the provided weight value.""" + +def determine_weight(volume, density): + if density < 0: + raise InvalidDensityError("Density must be positive") + if volume < 0: + raise InvalidVolumeError("Volume must be positive") + if volume == 0: + density / volume + + +print("Example 3") +class my_module: + Error = Error + InvalidDensityError = InvalidDensityError + + @staticmethod + def determine_weight(volume, density): + if density < 0: + raise InvalidDensityError("Density must be positive") + if volume < 0: + raise InvalidVolumeError("Volume must be positive") + if volume == 0: + density / volume + +try: + weight = my_module.determine_weight(1, -1) +except my_module.Error: + logging.exception("Unexpected error") +else: + assert False + + +print("Example 4") +SENTINEL = object() +weight = SENTINEL +try: + weight = my_module.determine_weight(-1, 1) +except my_module.InvalidDensityError: + weight = 0 +except my_module.Error: + logging.exception("Bug in the calling code") +else: + assert False + +assert weight is SENTINEL + + +print("Example 5") +try: + weight = SENTINEL + try: + weight = my_module.determine_weight(0, 1) + except my_module.InvalidDensityError: + weight = 0 + except my_module.Error: + logging.exception("Bug in the calling code") + except Exception: + logging.exception("Bug in the API code!") + raise # Re-raise exception to the caller + else: + assert False + + assert weight == 0 +except: + logging.exception('Expected') +else: + assert False + + +print("Example 6") +# my_module.py + +class NegativeDensityError(InvalidDensityError): + """A provided density value was negative.""" + + +def determine_weight(volume, density): + if density < 0: + raise NegativeDensityError("Density must be positive") + + +print("Example 7") +try: + my_module.NegativeDensityError = NegativeDensityError + my_module.determine_weight = determine_weight + try: + weight = my_module.determine_weight(1, -1) + except my_module.NegativeDensityError: + raise ValueError("Must supply non-negative density") + except my_module.InvalidDensityError: + weight = 0 + except my_module.Error: + logging.exception("Bug in the calling code") + except Exception: + logging.exception("Bug in the API code!") + raise + else: + assert False +except: + logging.exception('Expected') +else: + assert False + + +print("Example 8") +# my_module.py +class Error(Exception): + """Base-class for all exceptions raised by this module.""" + +class WeightError(Error): + """Base-class for weight calculation errors.""" + +class VolumeError(Error): + """Base-class for volume calculation errors.""" + +class DensityError(Error): + """Base-class for density calculation errors.""" diff --git a/example_code/item_52/recursive_import_dynamic/app.py b/example_code/item_122/recursive_import_bad/app.py similarity index 73% rename from example_code/item_52/recursive_import_dynamic/app.py rename to example_code/item_122/recursive_import_bad/app.py index 75fe7a6..b0dd91e 100755 --- a/example_code/item_52/recursive_import_dynamic/app.py +++ b/example_code/item_122/recursive_import_bad/app.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,16 +14,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 12 import dialog -class Prefs(object): +class Prefs: def get(self, name): pass diff --git a/example_code/item_52/recursive_import_bad/dialog.py b/example_code/item_122/recursive_import_bad/dialog.py similarity index 67% rename from example_code/item_52/recursive_import_bad/dialog.py rename to example_code/item_122/recursive_import_bad/dialog.py index adf7de7..b98cc57 100755 --- a/example_code/item_52/recursive_import_bad/dialog.py +++ b/example_code/item_122/recursive_import_bad/dialog.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,20 +14,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 2 import app -class Dialog(object): +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_52/recursive_import_ordering/main.py b/example_code/item_122/recursive_import_bad/main.py similarity index 73% rename from example_code/item_52/recursive_import_ordering/main.py rename to example_code/item_122/recursive_import_bad/main.py index dbd0992..bf57d21 100755 --- a/example_code/item_52/recursive_import_ordering/main.py +++ b/example_code/item_122/recursive_import_bad/main.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,11 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 5 import app diff --git a/example_code/item_52/recursive_import_bad/app.py b/example_code/item_122/recursive_import_dynamic/app.py similarity index 73% rename from example_code/item_52/recursive_import_bad/app.py rename to example_code/item_122/recursive_import_dynamic/app.py index 82139af..46aca3d 100755 --- a/example_code/item_52/recursive_import_bad/app.py +++ b/example_code/item_122/recursive_import_dynamic/app.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,18 +14,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 3 import dialog -class Prefs(object): +class Prefs: def get(self, name): pass + prefs = Prefs() dialog.show() diff --git a/example_code/item_52/recursive_import_dynamic/dialog.py b/example_code/item_122/recursive_import_dynamic/dialog.py similarity index 71% rename from example_code/item_52/recursive_import_dynamic/dialog.py rename to example_code/item_122/recursive_import_dynamic/dialog.py index d754019..2824cea 100755 --- a/example_code/item_52/recursive_import_dynamic/dialog.py +++ b/example_code/item_122/recursive_import_dynamic/dialog.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,25 +14,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 11 # Reenabling this will break things. # import app -class Dialog(object): +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_52/recursive_import_bad/main.py b/example_code/item_122/recursive_import_dynamic/main.py similarity index 73% rename from example_code/item_52/recursive_import_bad/main.py rename to example_code/item_122/recursive_import_dynamic/main.py index 14d24c6..bf57d21 100755 --- a/example_code/item_52/recursive_import_bad/main.py +++ b/example_code/item_122/recursive_import_dynamic/main.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,11 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 import app diff --git a/example_code/item_52/recursive_import_nosideeffects/app.py b/example_code/item_122/recursive_import_nosideeffects/app.py similarity index 74% rename from example_code/item_52/recursive_import_nosideeffects/app.py rename to example_code/item_122/recursive_import_nosideeffects/app.py index 4cbce45..10734f2 100755 --- a/example_code/item_52/recursive_import_nosideeffects/app.py +++ b/example_code/item_122/recursive_import_nosideeffects/app.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,19 +14,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 8 import dialog -class Prefs(object): +class Prefs: def get(self, name): pass + prefs = Prefs() def configure(): diff --git a/example_code/item_52/recursive_import_nosideeffects/dialog.py b/example_code/item_122/recursive_import_nosideeffects/dialog.py similarity index 67% rename from example_code/item_52/recursive_import_nosideeffects/dialog.py rename to example_code/item_122/recursive_import_nosideeffects/dialog.py index 61fe151..0138142 100755 --- a/example_code/item_52/recursive_import_nosideeffects/dialog.py +++ b/example_code/item_122/recursive_import_nosideeffects/dialog.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,23 +14,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 7 import app -class Dialog(object): +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_52/recursive_import_nosideeffects/main.py b/example_code/item_122/recursive_import_nosideeffects/main.py similarity index 75% rename from example_code/item_52/recursive_import_nosideeffects/main.py rename to example_code/item_122/recursive_import_nosideeffects/main.py index 27174fd..7a39f76 100755 --- a/example_code/item_52/recursive_import_nosideeffects/main.py +++ b/example_code/item_122/recursive_import_nosideeffects/main.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 9 import app import dialog diff --git a/example_code/item_52/recursive_import_ordering/app.py b/example_code/item_122/recursive_import_ordering/app.py similarity index 74% rename from example_code/item_52/recursive_import_ordering/app.py rename to example_code/item_122/recursive_import_ordering/app.py index 296116e..12b9e5f 100755 --- a/example_code/item_52/recursive_import_ordering/app.py +++ b/example_code/item_122/recursive_import_ordering/app.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,18 +14,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 6 -class Prefs(object): +class Prefs: def get(self, name): pass + prefs = Prefs() import dialog # Moved + dialog.show() diff --git a/example_code/item_52/recursive_import_ordering/dialog.py b/example_code/item_122/recursive_import_ordering/dialog.py similarity index 67% rename from example_code/item_52/recursive_import_ordering/dialog.py rename to example_code/item_122/recursive_import_ordering/dialog.py index 95c86a8..f36cb22 100755 --- a/example_code/item_52/recursive_import_ordering/dialog.py +++ b/example_code/item_122/recursive_import_ordering/dialog.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,20 +14,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 4 import app -class Dialog(object): + +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_52/recursive_import_dynamic/main.py b/example_code/item_122/recursive_import_ordering/main.py similarity index 73% rename from example_code/item_52/recursive_import_dynamic/main.py rename to example_code/item_122/recursive_import_ordering/main.py index 7ffd123..bf57d21 100755 --- a/example_code/item_52/recursive_import_dynamic/main.py +++ b/example_code/item_122/recursive_import_ordering/main.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python3 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,11 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 10 import app diff --git a/example_code/item_123.py b/example_code/item_123.py new file mode 100755 index 0000000..be85f79 --- /dev/null +++ b/example_code/item_123.py @@ -0,0 +1,276 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +def print_distance(speed, duration): + distance = speed * duration + print(f"{distance} miles") + +print_distance(5, 2.5) + + +print("Example 2") +print_distance(1000, 3) + + +print("Example 3") +CONVERSIONS = { + "mph": 1.60934 / 3600 * 1000, # m/s + "hours": 3600, # seconds + "miles": 1.60934 * 1000, # m + "meters": 1, # m + "m/s": 1, # m/s + "seconds": 1, # s +} + +def convert(value, units): + rate = CONVERSIONS[units] + return rate * value + +def localize(value, units): + rate = CONVERSIONS[units] + return value / rate + +def print_distance( + speed, + duration, + *, + speed_units="mph", + time_units="hours", + distance_units="miles", +): + norm_speed = convert(speed, speed_units) + norm_duration = convert(duration, time_units) + norm_distance = norm_speed * norm_duration + distance = localize(norm_distance, distance_units) + print(f"{distance} {distance_units}") + + +print("Example 4") +print_distance( + 1000, + 3, + speed_units="meters", + time_units="seconds", +) + + +print("Example 5") +import warnings + +def print_distance( + speed, + duration, + *, + speed_units=None, + time_units=None, + distance_units=None, +): + if speed_units is None: + warnings.warn( + "speed_units required", + DeprecationWarning, + ) + speed_units = "mph" + + if time_units is None: + warnings.warn( + "time_units required", + DeprecationWarning, + ) + time_units = "hours" + + if distance_units is None: + warnings.warn( + "distance_units required", + DeprecationWarning, + ) + distance_units = "miles" + + norm_speed = convert(speed, speed_units) + norm_duration = convert(duration, time_units) + norm_distance = norm_speed * norm_duration + distance = localize(norm_distance, distance_units) + print(f"{distance} {distance_units}") + + +print("Example 6") +import contextlib +import io + +fake_stderr = io.StringIO() +with contextlib.redirect_stderr(fake_stderr): + print_distance( + 1000, + 3, + speed_units="meters", + time_units="seconds", + ) + +print(fake_stderr.getvalue()) + + +print("Example 7") +def require(name, value, default): + if value is not None: + return value + warnings.warn( + f"{name} will be required soon, update your code", + DeprecationWarning, + stacklevel=3, + ) + return default + +def print_distance( + speed, + duration, + *, + speed_units=None, + time_units=None, + distance_units=None, +): + speed_units = require( + "speed_units", + speed_units, + "mph", + ) + time_units = require( + "time_units", + time_units, + "hours", + ) + distance_units = require( + "distance_units", + distance_units, + "miles", + ) + + norm_speed = convert(speed, speed_units) + norm_duration = convert(duration, time_units) + norm_distance = norm_speed * norm_duration + distance = localize(norm_distance, distance_units) + print(f"{distance} {distance_units}") + + +print("Example 8") +import contextlib +import io + +fake_stderr = io.StringIO() +with contextlib.redirect_stderr(fake_stderr): + print_distance( + 1000, + 3, + speed_units="meters", + time_units="seconds", + ) + +print(fake_stderr.getvalue()) + + +print("Example 9") +warnings.simplefilter("error") +try: + warnings.warn( + "This usage is deprecated", + DeprecationWarning, + ) +except DeprecationWarning: + pass # Expected +else: + assert False + +warnings.resetwarnings() + + +print("Example 10") +warnings.resetwarnings() + +warnings.simplefilter("ignore") +warnings.warn("This will not be printed to stderr") + +warnings.resetwarnings() + + +print("Example 11") +import logging + +fake_stderr = io.StringIO() +handler = logging.StreamHandler(fake_stderr) +formatter = logging.Formatter("%(asctime)-15s WARNING] %(message)s") +handler.setFormatter(formatter) + +logging.captureWarnings(True) +logger = logging.getLogger("py.warnings") +logger.addHandler(handler) +logger.setLevel(logging.DEBUG) + +warnings.resetwarnings() +warnings.simplefilter("default") +warnings.warn("This will go to the logs output") + +print(fake_stderr.getvalue()) + +warnings.resetwarnings() + + +print("Example 12") +with warnings.catch_warnings(record=True) as found_warnings: + found = require("my_arg", None, "fake units") + expected = "fake units" + assert found == expected + + +print("Example 13") +assert len(found_warnings) == 1 +single_warning = found_warnings[0] +assert str(single_warning.message) == ( + "my_arg will be required soon, update your code" +) +assert single_warning.category == DeprecationWarning diff --git a/example_code/item_124.py b/example_code/item_124.py new file mode 100755 index 0000000..7510fda --- /dev/null +++ b/example_code/item_124.py @@ -0,0 +1,180 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Start book environment setup +import random +random.seed(1234) + +import logging +from pprint import pprint +from sys import stdout as STDOUT + +# Write all output to a temporary directory +import atexit +import gc +import io +import os +import tempfile + +TEST_DIR = tempfile.TemporaryDirectory() +atexit.register(TEST_DIR.cleanup) + +# Make sure Windows processes exit cleanly +OLD_CWD = os.getcwd() +atexit.register(lambda: os.chdir(OLD_CWD)) +os.chdir(TEST_DIR.name) + +def close_open_files(): + everything = gc.get_objects() + for obj in everything: + if isinstance(obj, io.IOBase): + obj.close() + +atexit.register(close_open_files) +### End book environment setup + + +print("Example 1") +try: + def subtract(a, b): + return a - b + + subtract(10, "5") +except: + logging.exception('Expected') +else: + assert False + + +print("Example 3") +class Counter: + def __init__(self): + self.value = 0 + + def add(self, offset): + value += offset + + def get(self) -> int: + self.value + + +print("Example 4") +try: + counter = Counter() + counter.add(5) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 5") +try: + counter = Counter() + found = counter.get() + assert found == 0, found +except: + logging.exception('Expected') +else: + assert False + + +print("Example 7") +try: + def combine(func, values): + assert len(values) > 0 + + result = values[0] + for next_value in values[1:]: + result = func(result, next_value) + + return result + + def add(x, y): + return x + y + + inputs = [1, 2, 3, 4j] + result = combine(add, inputs) + assert result == 10, result # Fails +except: + logging.exception('Expected') +else: + assert False + + +print("Example 9") +try: + def get_or_default(value, default): + if value is not None: + return value + return value + + found = get_or_default(3, 5) + assert found == 3 + + found = get_or_default(None, 5) + assert found == 5, found # Fails +except: + logging.exception('Expected') +else: + assert False + + +print("Example 11") +class FirstClass: + def __init__(self, value): + self.value = value + +class SecondClass: + def __init__(self, value): + self.value = value + +second = SecondClass(5) +first = FirstClass(second) + +del FirstClass +del SecondClass + + +print("Example 13") +try: + class FirstClass: + def __init__(self, value: SecondClass) -> None: # Breaks + self.value = value + + class SecondClass: + def __init__(self, value: int) -> None: + self.value = value + + second = SecondClass(5) + first = FirstClass(second) +except: + logging.exception('Expected') +else: + assert False + + +print("Example 14") +class FirstClass: + def __init__(self, value: "SecondClass") -> None: # OK + self.value = value + +class SecondClass: + def __init__(self, value: int) -> None: + self.value = value + +second = SecondClass(5) +first = FirstClass(second) diff --git a/example_code/item_124_example_02.py b/example_code/item_124_example_02.py new file mode 100755 index 0000000..5152d85 --- /dev/null +++ b/example_code/item_124_example_02.py @@ -0,0 +1,25 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 2") +# Check types in this file with: python3 -m mypy + +def subtract(a: int, b: int) -> int: # Function annotation + return a - b + +subtract(10, "5") # Oops: passed string value diff --git a/example_code/item_124_example_06.py b/example_code/item_124_example_06.py new file mode 100755 index 0000000..9dd5763 --- /dev/null +++ b/example_code/item_124_example_06.py @@ -0,0 +1,35 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 6") +# Check types in this file with: python3 -m mypy + +class Counter: + def __init__(self) -> None: + self.value: int = 0 # Field / variable annotation + + def add(self, offset: int) -> None: + value += offset # Oops: forgot "self." + + def get(self) -> int: + self.value # Oops: forgot "return" + +counter = Counter() +counter.add(5) +counter.add(3) +assert counter.get() == 8 diff --git a/example_code/item_124_example_08.py b/example_code/item_124_example_08.py new file mode 100755 index 0000000..d27afe6 --- /dev/null +++ b/example_code/item_124_example_08.py @@ -0,0 +1,44 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 8") +# Check types in this file with: python3 -m mypy + +from collections.abc import Callable +from typing import TypeVar + +Value = TypeVar("Value") +Func = Callable[[Value, Value], Value] + +def combine(func: Func[Value], values: list[Value]) -> Value: + assert len(values) > 0 + + result = values[0] + for next_value in values[1:]: + result = func(result, next_value) + + return result + +Real = TypeVar("Real", int, float) + +def add(x: Real, y: Real) -> Real: + return x + y + +inputs = [1, 2, 3, 4j] # Oops: included a complex number +result = combine(add, inputs) +assert result == 10 diff --git a/example_code/item_21_example_10.py b/example_code/item_124_example_10.py similarity index 61% rename from example_code/item_21_example_10.py rename to example_code/item_124_example_10.py index de987a5..15a9efb 100755 --- a/example_code/item_21_example_10.py +++ b/example_code/item_124_example_10.py @@ -1,6 +1,6 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env PYTHONHASHSEED=1234 python3 -# Copyright 2014 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,15 +14,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT -# Example 10 -def print_args(*args, **kwargs): - print 'Positional:', args - print 'Keyword: ', kwargs +print("Example 10") +# Check types in this file with: python3 -m mypy -print_args(1, 2, foo='bar', stuff='meep') +def get_or_default(value: int | None, default: int) -> int: + if value is not None: + return value + return value # Oops: should have returned "default" diff --git a/example_code/item_124_example_12.py b/example_code/item_124_example_12.py new file mode 100755 index 0000000..78fbfed --- /dev/null +++ b/example_code/item_124_example_12.py @@ -0,0 +1,31 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +print("Example 12") +# Check types in this file with: python3 -m mypy + +class FirstClass: + def __init__(self, value: SecondClass) -> None: + self.value = value + +class SecondClass: + def __init__(self, value: int) -> None: + self.value = value + +second = SecondClass(5) +first = FirstClass(second) diff --git a/example_code/item_125/zipimport_examples/django_pkgutil.py b/example_code/item_125/zipimport_examples/django_pkgutil.py new file mode 100755 index 0000000..5029358 --- /dev/null +++ b/example_code/item_125/zipimport_examples/django_pkgutil.py @@ -0,0 +1,24 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# django_pkgutil.py +import pkgutil + +data = pkgutil.get_data( + "django.conf.locale", + "en/LC_MESSAGES/django.po", +) +print(data.decode("utf-8")) diff --git a/example_code/item_125/zipimport_examples/trans_real.py b/example_code/item_125/zipimport_examples/trans_real.py new file mode 100755 index 0000000..5b78b7d --- /dev/null +++ b/example_code/item_125/zipimport_examples/trans_real.py @@ -0,0 +1,29 @@ +#!/usr/bin/env PYTHONHASHSEED=1234 python3 + +# Copyright 2014-2024 Brett Slatkin, Pearson Education Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# trans_real.py +# Copyright (c) Django Software Foundation and +# individual contributors. All rights reserved. +class DjangoTranslation(gettext_module.GNUTranslations): + + def _init_translation_catalog(self): + settingsfile = sys.modules[settings.__module__].__file__ + localedir = os.path.join( + os.path.dirname(settingsfile), + "locale", + ) + translation = self._new_gnu_trans(localedir) + self.merge(translation) diff --git a/example_code/item_13.py b/example_code/item_13.py deleted file mode 100755 index d553a51..0000000 --- a/example_code/item_13.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -handle = open('random_data.txt', 'w', encoding='utf-8') -handle.write('success\nand\nnew\nlines') -handle.close() -handle = open('random_data.txt') # May raise IOError -try: - data = handle.read() # May raise UnicodeDecodeError -finally: - handle.close() # Always runs after try: - - -# Example 2 -import json - -def load_json_key(data, key): - try: - result_dict = json.loads(data) # May raise ValueError - except ValueError as e: - raise KeyError from e - else: - return result_dict[key] # May raise KeyError - -# JSON decode successful -assert load_json_key('{"foo": "bar"}', 'foo') == 'bar' -try: - load_json_key('{"foo": "bar"}', 'does not exist') - assert False -except KeyError: - pass # Expected - -# JSON decode fails -try: - load_json_key('{"foo": bad payload', 'foo') - assert False -except KeyError: - pass # Expected - - -# Example 3 -import json -UNDEFINED = object() - -def divide_json(path): - handle = open(path, 'r+') # May raise IOError - try: - data = handle.read() # May raise UnicodeDecodeError - op = json.loads(data) # May raise ValueError - value = ( - op['numerator'] / - op['denominator']) # May raise ZeroDivisionError - except ZeroDivisionError as e: - return UNDEFINED - else: - op['result'] = value - result = json.dumps(op) - handle.seek(0) - handle.write(result) # May raise IOError - return value - finally: - handle.close() # Always runs - -# Everything works -temp_path = 'random_data.json' -handle = open(temp_path, 'w') -handle.write('{"numerator": 1, "denominator": 10}') -handle.close() -assert divide_json(temp_path) == 0.1 - -# Divide by Zero error -handle = open(temp_path, 'w') -handle.write('{"numerator": 1, "denominator": 0}') -handle.close() -assert divide_json(temp_path) is UNDEFINED - -# JSON decode error -handle = open(temp_path, 'w') -handle.write('{"numerator": 1 bad data') -handle.close() -try: - divide_json(temp_path) - assert False -except ValueError: - pass # Expected diff --git a/example_code/item_14.py b/example_code/item_14.py deleted file mode 100755 index bb00554..0000000 --- a/example_code/item_14.py +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def divide(a, b): - try: - return a / b - except ZeroDivisionError: - return None - -assert divide(4, 2) == 2 -assert divide(0, 1) == 0 -assert divide(3, 6) == 0.5 -assert divide(1, 0) == None - - -# Example 2 -x, y = 1, 0 -result = divide(x, y) -if result is None: - print('Invalid inputs') -else: - print('Result is %.1f' % result) - - -# Example 3 -x, y = 0, 5 -result = divide(x, y) -if not result: - print('Invalid inputs') # This is wrong! -else: - assert False - - -# Example 4 -def divide(a, b): - try: - return True, a / b - except ZeroDivisionError: - return False, None - - -# Example 5 -x, y = 5, 0 -success, result = divide(x, y) -if not success: - print('Invalid inputs') - - -# Example 6 -x, y = 5, 0 -_, result = divide(x, y) -if not result: - print('Invalid inputs') # This is right - -x, y = 0, 5 -_, result = divide(x, y) -if not result: - print('Invalid inputs') # This is wrong - - -# Example 7 -def divide(a, b): - try: - return a / b - except ZeroDivisionError as e: - raise ValueError('Invalid inputs') from e - - -# Example 8 -x, y = 5, 2 -try: - result = divide(x, y) -except ValueError: - print('Invalid inputs') -else: - print('Result is %.1f' % result) diff --git a/example_code/item_15_example_09.py b/example_code/item_15_example_09.py deleted file mode 100755 index 474d64e..0000000 --- a/example_code/item_15_example_09.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 9 -def sort_priority(numbers, group): - found = [False] - def helper(x): - if x in group: - found[0] = True - return (0, x) - return (1, x) - numbers.sort(key=helper) - return found[0] - -numbers = [8, 3, 1, 2, 5, 4, 7, 6] -group = set([2, 3, 5, 7]) -found = sort_priority(numbers, group) -print('Found:', found) -print(numbers) diff --git a/example_code/item_17.py b/example_code/item_17.py deleted file mode 100755 index 75fd7e4..0000000 --- a/example_code/item_17.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def normalize(numbers): - total = sum(numbers) - result = [] - for value in numbers: - percent = 100 * value / total - result.append(percent) - return result - - -# Example 2 -visits = [15, 35, 80] -percentages = normalize(visits) -print(percentages) - - -# Example 3 -path = 'my_numbers.txt' -with open(path, 'w') as f: - for i in (15, 35, 80): - f.write('%d\n' % i) - -def read_visits(data_path): - with open(data_path) as f: - for line in f: - yield int(line) - - -# Example 4 -it = read_visits('my_numbers.txt') -percentages = normalize(it) -print(percentages) - - -# Example 5 -it = read_visits('my_numbers.txt') -print(list(it)) -print(list(it)) # Already exhausted - - -# Example 6 -def normalize_copy(numbers): - numbers = list(numbers) # Copy the iterator - total = sum(numbers) - result = [] - for value in numbers: - percent = 100 * value / total - result.append(percent) - return result - - -# Example 7 -it = read_visits('my_numbers.txt') -percentages = normalize_copy(it) -print(percentages) - - -# Example 8 -def normalize_func(get_iter): - total = sum(get_iter()) # New iterator - result = [] - for value in get_iter(): # New iterator - percent = 100 * value / total - result.append(percent) - return result - - -# Example 9 -percentages = normalize_func(lambda: read_visits(path)) -print(percentages) - - -# Example 10 -class ReadVisits(object): - def __init__(self, data_path): - self.data_path = data_path - - def __iter__(self): - with open(self.data_path) as f: - for line in f: - yield int(line) - - -# Example 11 -visits = ReadVisits(path) -percentages = normalize(visits) -print(percentages) - - -# Example 12 -def normalize_defensive(numbers): - if iter(numbers) is iter(numbers): # An iterator -- bad! - raise TypeError('Must supply a container') - total = sum(numbers) - result = [] - for value in numbers: - percent = 100 * value / total - result.append(percent) - return result - - -# Example 13 -visits = [15, 35, 80] -normalize_defensive(visits) # No error -visits = ReadVisits(path) -normalize_defensive(visits) # No error - - -# Example 14 -try: - it = iter(visits) - normalize_defensive(it) -except: - logging.exception('Expected') -else: - assert False diff --git a/example_code/item_18.py b/example_code/item_18.py deleted file mode 100755 index f3ac9e2..0000000 --- a/example_code/item_18.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def log(message, values): - if not values: - print(message) - else: - values_str = ', '.join(str(x) for x in values) - print('%s: %s' % (message, values_str)) - -log('My numbers are', [1, 2]) -log('Hi there', []) - - -# Example 2 -def log(message, *values): # The only difference - if not values: - print(message) - else: - values_str = ', '.join(str(x) for x in values) - print('%s: %s' % (message, values_str)) - -log('My numbers are', 1, 2) -log('Hi there') # Much better - - -# Example 3 -favorites = [7, 33, 99] -log('Favorite colors', *favorites) - - -# Example 4 -def my_generator(): - for i in range(10): - yield i - -def my_func(*args): - print(args) - -it = my_generator() -my_func(*it) - - -# Example 5 -def log(sequence, message, *values): - if not values: - print('%s: %s' % (sequence, message)) - else: - values_str = ', '.join(str(x) for x in values) - print('%s: %s: %s' % (sequence, message, values_str)) - -log(1, 'Favorites', 7, 33) # New usage is OK -log('Favorite numbers', 7, 33) # Old usage breaks diff --git a/example_code/item_19.py b/example_code/item_19.py deleted file mode 100755 index bc529fa..0000000 --- a/example_code/item_19.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def remainder(number, divisor): - return number % divisor - -assert remainder(20, 7) == 6 - - -# Example 2 -remainder(20, 7) -remainder(20, divisor=7) -remainder(number=20, divisor=7) -remainder(divisor=7, number=20) - - -# Example 3 -try: - # This will not compile - source = """remainder(number=20, 7)""" - eval(source) -except: - logging.exception('Expected') -else: - assert False - - -# Example 4 -try: - remainder(20, number=7) -except: - logging.exception('Expected') -else: - assert False - - -# Example 5 -def flow_rate(weight_diff, time_diff): - return weight_diff / time_diff - -weight_diff = 0.5 -time_diff = 3 -flow = flow_rate(weight_diff, time_diff) -print('%.3f kg per second' % flow) -assert (flow - 0.16666666666666666) < 0.0001 - - -# Example 6 -def flow_rate(weight_diff, time_diff, period): - return (weight_diff / time_diff) * period - - -# Example 7 -flow_per_second = flow_rate(weight_diff, time_diff, 1) -assert (flow_per_second - 0.16666666666666666) < 0.0001 - - -# Example 8 -def flow_rate(weight_diff, time_diff, period=1): - return (weight_diff / time_diff) * period - - -# Example 9 -flow_per_second = flow_rate(weight_diff, time_diff) -assert (flow_per_second - 0.16666666666666666) < 0.0001 -flow_per_hour = flow_rate(weight_diff, time_diff, period=3600) -assert flow_per_hour == 600.0 - - -# Example 10 -def flow_rate(weight_diff, time_diff, - period=1, units_per_kg=1): - return ((weight_diff * units_per_kg) / time_diff) * period - - -# Example 11 -pounds_per_hour = flow_rate(weight_diff, time_diff, - period=3600, units_per_kg=2.2) -print(pounds_per_hour) -assert pounds_per_hour == 1320.0 - - -# Example 12 -pounds_per_hour = flow_rate(weight_diff, time_diff, 3600, 2.2) -print(pounds_per_hour) -assert pounds_per_hour == 1320.0 diff --git a/example_code/item_21.py b/example_code/item_21.py deleted file mode 100755 index d8cc7c4..0000000 --- a/example_code/item_21.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def safe_division(number, divisor, ignore_overflow, - ignore_zero_division): - try: - return number / divisor - except OverflowError: - if ignore_overflow: - return 0 - else: - raise - except ZeroDivisionError: - if ignore_zero_division: - return float('inf') - else: - raise - - -# Example 2 -result = safe_division(1.0, 10**500, True, False) -print(result) -assert result is 0 - - -# Example 3 -result = safe_division(1.0, 0, False, True) -print(result) -assert result == float('inf') - - -# Example 4 -def safe_division_b(number, divisor, - ignore_overflow=False, - ignore_zero_division=False): - try: - return number / divisor - except OverflowError: - if ignore_overflow: - return 0 - else: - raise - except ZeroDivisionError: - if ignore_zero_division: - return float('inf') - else: - raise - - -# Example 5 -assert safe_division_b(1.0, 10**500, ignore_overflow=True) is 0 -assert safe_division_b(1.0, 0, ignore_zero_division=True) == float('inf') - - -# Example 6 -assert safe_division_b(1.0, 10**500, True, False) is 0 - - -# Example 7 -def safe_division_c(number, divisor, *, - ignore_overflow=False, - ignore_zero_division=False): - try: - return number / divisor - except OverflowError: - if ignore_overflow: - return 0 - else: - raise - except ZeroDivisionError: - if ignore_zero_division: - return float('inf') - else: - raise - - -# Example 8 -try: - safe_division_c(1.0, 10**500, True, False) -except: - logging.exception('Expected') -else: - assert False - - -# Example 9 -safe_division_c(1.0, 0, ignore_zero_division=True) # No exception -try: - safe_division_c(1.0, 0) - assert False -except ZeroDivisionError: - pass # Expected diff --git a/example_code/item_21_example_11.py b/example_code/item_21_example_11.py deleted file mode 100755 index 5788e25..0000000 --- a/example_code/item_21_example_11.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 11 -def safe_division_d(number, divisor, **kwargs): - ignore_overflow = kwargs.pop('ignore_overflow', False) - ignore_zero_div = kwargs.pop('ignore_zero_division', False) - if kwargs: - raise TypeError('Unexpected **kwargs: %r' % kwargs) - try: - return number / divisor - except OverflowError: - if ignore_overflow: - return 0 - else: - raise - except ZeroDivisionError: - if ignore_zero_div: - return float('inf') - else: - raise - -assert safe_division_d(1.0, 10) == 0.1 -assert safe_division_d(1.0, 0, ignore_zero_division=True) == float('inf') -assert safe_division_d(1.0, 10**500, ignore_overflow=True) is 0 - -# Example 12 -try: - safe_division_d(1.0, 0, False, True) -except: - logging.exception('Expected') -else: - assert False - - -# Example 13 -try: - safe_division_d(0.0, 0, unexpected=True) -except: - logging.exception('Expected') -else: - assert False diff --git a/example_code/item_25.py b/example_code/item_25.py deleted file mode 100755 index 7190b68..0000000 --- a/example_code/item_25.py +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class MyBaseClass(object): - def __init__(self, value): - self.value = value - -class MyChildClass(MyBaseClass): - def __init__(self): - MyBaseClass.__init__(self, 5) - - def times_two(self): - return self.value * 2 - -foo = MyChildClass() -print(foo.times_two()) - - -# Example 2 -class TimesTwo(object): - def __init__(self): - self.value *= 2 - -class PlusFive(object): - def __init__(self): - self.value += 5 - - -# Example 3 -class OneWay(MyBaseClass, TimesTwo, PlusFive): - def __init__(self, value): - MyBaseClass.__init__(self, value) - TimesTwo.__init__(self) - PlusFive.__init__(self) - - -# Example 4 -foo = OneWay(5) -print('First ordering is (5 * 2) + 5 =', foo.value) - - -# Example 5 -class AnotherWay(MyBaseClass, PlusFive, TimesTwo): - def __init__(self, value): - MyBaseClass.__init__(self, value) - TimesTwo.__init__(self) - PlusFive.__init__(self) - - -# Example 6 -bar = AnotherWay(5) -print('Second ordering still is', bar.value) - - -# Example 7 -class TimesFive(MyBaseClass): - def __init__(self, value): - MyBaseClass.__init__(self, value) - self.value *= 5 - -class PlusTwo(MyBaseClass): - def __init__(self, value): - MyBaseClass.__init__(self, value) - self.value += 2 - - -# Example 8 -class ThisWay(TimesFive, PlusTwo): - def __init__(self, value): - TimesFive.__init__(self, value) - PlusTwo.__init__(self, value) - -foo = ThisWay(5) -print('Should be (5 * 5) + 2 = 27 but is', foo.value) - - -# Example 11 -# This is pretending to be Python 2 but it's not -class MyBaseClass(object): - def __init__(self, value): - self.value = value - -class TimesFiveCorrect(MyBaseClass): - def __init__(self, value): - super(TimesFiveCorrect, self).__init__(value) - self.value *= 5 - -class PlusTwoCorrect(MyBaseClass): - def __init__(self, value): - super(PlusTwoCorrect, self).__init__(value) - self.value += 2 - -class GoodWay(TimesFiveCorrect, PlusTwoCorrect): - def __init__(self, value): - super(GoodWay, self).__init__(value) - -before_pprint = pprint -pprint(GoodWay.mro()) -from pprint import pprint -pprint(GoodWay.mro()) -pprint = pprint - - -# Example 12 -class Explicit(MyBaseClass): - def __init__(self, value): - super(__class__, self).__init__(value * 2) - -class Implicit(MyBaseClass): - def __init__(self, value): - super().__init__(value * 2) - -assert Explicit(10).value == Implicit(10).value diff --git a/example_code/item_25_example_09.py b/example_code/item_25_example_09.py deleted file mode 100755 index 1955956..0000000 --- a/example_code/item_25_example_09.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 9 -class MyBaseClass(object): - def __init__(self, value): - self.value = value - -class TimesFiveCorrect(MyBaseClass): - def __init__(self, value): - super(TimesFiveCorrect, self).__init__(value) - self.value *= 5 - -class PlusTwoCorrect(MyBaseClass): - def __init__(self, value): - super(PlusTwoCorrect, self).__init__(value) - self.value += 2 diff --git a/example_code/item_25_example_10.py b/example_code/item_25_example_10.py deleted file mode 100755 index b632b11..0000000 --- a/example_code/item_25_example_10.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 10 -class MyBaseClass(object): - def __init__(self, value): - self.value = value - -class TimesFiveCorrect(MyBaseClass): - def __init__(self, value): - super(TimesFiveCorrect, self).__init__(value) - self.value *= 5 - -class PlusTwoCorrect(MyBaseClass): - def __init__(self, value): - super(PlusTwoCorrect, self).__init__(value) - self.value += 2 - -class GoodWay(TimesFiveCorrect, PlusTwoCorrect): - def __init__(self, value): - super(GoodWay, self).__init__(value) - -foo = GoodWay(5) -print 'Should be 5 * (5 + 2) = 35 and is', foo.value diff --git a/example_code/item_28.py b/example_code/item_28.py deleted file mode 100755 index d09458a..0000000 --- a/example_code/item_28.py +++ /dev/null @@ -1,165 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class FrequencyList(list): - def __init__(self, members): - super().__init__(members) - - def frequency(self): - counts = {} - for item in self: - counts.setdefault(item, 0) - counts[item] += 1 - return counts - - -# Example 2 -foo = FrequencyList(['a', 'b', 'a', 'c', 'b', 'a', 'd']) -print('Length is', len(foo)) -foo.pop() -print('After pop:', repr(foo)) -print('Frequency:', foo.frequency()) - - -# Example 3 -class BinaryNode(object): - def __init__(self, value, left=None, right=None): - self.value = value - self.left = left - self.right = right - - -# Example 4 -bar = [1, 2, 3] -bar[0] - - -# Example 5 -bar.__getitem__(0) - - -# Example 6 -class IndexableNode(BinaryNode): - def _search(self, count, index): - found = None - if self.left: - found, count = self.left._search(count, index) - if not found and count == index: - found = self - else: - count += 1 - if not found and self.right: - found, count = self.right._search(count, index) - return found, count - # Returns (found, count) - - def __getitem__(self, index): - found, _ = self._search(0, index) - if not found: - raise IndexError('Index out of range') - return found.value - - -# Example 7 -tree = IndexableNode( - 10, - left=IndexableNode( - 5, - left=IndexableNode(2), - right=IndexableNode( - 6, right=IndexableNode(7))), - right=IndexableNode( - 15, left=IndexableNode(11))) - - -# Example 8 -print('LRR =', tree.left.right.right.value) -print('Index 0 =', tree[0]) -print('Index 1 =', tree[1]) -print('11 in the tree?', 11 in tree) -print('17 in the tree?', 17 in tree) -print('Tree is', list(tree)) - - -# Example 9 -try: - len(tree) -except: - logging.exception('Expected') -else: - assert False - - -# Example 10 -class SequenceNode(IndexableNode): - def __len__(self): - _, count = self._search(0, None) - return count - - -# Example 11 -tree = SequenceNode( - 10, - left=SequenceNode( - 5, - left=SequenceNode(2), - right=SequenceNode( - 6, right=SequenceNode(7))), - right=SequenceNode( - 15, left=SequenceNode(11)) -) - -print('Tree has %d nodes' % len(tree)) - - -# Example 12 -try: - from collections.abc import Sequence - - class BadType(Sequence): - pass - - foo = BadType() -except: - logging.exception('Expected') -else: - assert False - - -# Example 13 -class BetterNode(SequenceNode, Sequence): - pass - -tree = BetterNode( - 10, - left=BetterNode( - 5, - left=BetterNode(2), - right=BetterNode( - 6, right=BetterNode(7))), - right=BetterNode( - 15, left=BetterNode(11)) -) - -print('Index of 7 is', tree.index(7)) -print('Count of 10 is', tree.count(10)) diff --git a/example_code/item_30.py b/example_code/item_30.py deleted file mode 100755 index 47c898c..0000000 --- a/example_code/item_30.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -from datetime import datetime, timedelta - -class Bucket(object): - def __init__(self, period): - self.period_delta = timedelta(seconds=period) - self.reset_time = datetime.now() - self.quota = 0 - - def __repr__(self): - return 'Bucket(quota=%d)' % self.quota - -bucket = Bucket(60) -print(bucket) - - -# Example 2 -def fill(bucket, amount): - now = datetime.now() - if now - bucket.reset_time > bucket.period_delta: - bucket.quota = 0 - bucket.reset_time = now - bucket.quota += amount - - -# Example 3 -def deduct(bucket, amount): - now = datetime.now() - if now - bucket.reset_time > bucket.period_delta: - return False - if bucket.quota - amount < 0: - return False - bucket.quota -= amount - return True - - -# Example 4 -bucket = Bucket(60) -fill(bucket, 100) -print(bucket) - - -# Example 5 -if deduct(bucket, 99): - print('Had 99 quota') -else: - print('Not enough for 99 quota') -print(bucket) - - -# Example 6 -if deduct(bucket, 3): - print('Had 3 quota') -else: - print('Not enough for 3 quota') -print(bucket) - - -# Example 7 -class Bucket(object): - def __init__(self, period): - self.period_delta = timedelta(seconds=period) - self.reset_time = datetime.now() - self.max_quota = 0 - self.quota_consumed = 0 - - def __repr__(self): - return ('Bucket(max_quota=%d, quota_consumed=%d)' % - (self.max_quota, self.quota_consumed)) - - -# Example 8 - @property - def quota(self): - return self.max_quota - self.quota_consumed - - -# Example 9 - @quota.setter - def quota(self, amount): - delta = self.max_quota - amount - if amount == 0: - # Quota being reset for a new period - self.quota_consumed = 0 - self.max_quota = 0 - elif delta < 0: - # Quota being filled for the new period - assert self.quota_consumed == 0 - self.max_quota = amount - else: - # Quota being consumed during the period - assert self.max_quota >= self.quota_consumed - self.quota_consumed += delta - - -# Example 10 -bucket = Bucket(60) -print('Initial', bucket) -fill(bucket, 100) -print('Filled', bucket) - -if deduct(bucket, 99): - print('Had 99 quota') -else: - print('Not enough for 99 quota') - -print('Now', bucket) - -if deduct(bucket, 3): - print('Had 3 quota') -else: - print('Not enough for 3 quota') - -print('Still', bucket) diff --git a/example_code/item_31.py b/example_code/item_31.py deleted file mode 100755 index aa9c6de..0000000 --- a/example_code/item_31.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class Homework(object): - def __init__(self): - self._grade = 0 - - @property - def grade(self): - return self._grade - - @grade.setter - def grade(self, value): - if not (0 <= value <= 100): - raise ValueError('Grade must be between 0 and 100') - self._grade = value - - -# Example 2 -galileo = Homework() -galileo.grade = 95 -print(galileo.grade) - - -# Example 3 -class Exam(object): - def __init__(self): - self._writing_grade = 0 - self._math_grade = 0 - - @staticmethod - def _check_grade(value): - if not (0 <= value <= 100): - raise ValueError('Grade must be between 0 and 100') - - -# Example 4 - @property - def writing_grade(self): - return self._writing_grade - - @writing_grade.setter - def writing_grade(self, value): - self._check_grade(value) - self._writing_grade = value - - @property - def math_grade(self): - return self._math_grade - - @math_grade.setter - def math_grade(self, value): - self._check_grade(value) - self._math_grade = value - - -# Example 5 -galileo = Exam() -galileo.writing_grade = 85 -galileo.math_grade = 99 -print('Writing: %5r' % galileo.writing_grade) -print('Math: %5r' % galileo.math_grade) - - -# Example 6 -class Grade(object): - def __get__(*args, **kwargs): - pass - - def __set__(*args, **kwargs): - pass - -class Exam(object): - # Class attributes - math_grade = Grade() - writing_grade = Grade() - science_grade = Grade() - - -# Example 7 -exam = Exam() -exam.writing_grade = 40 - - -# Example 8 -Exam.__dict__['writing_grade'].__set__(exam, 40) - - -# Example 9 -print(exam.writing_grade) - - -# Example 10 -print(Exam.__dict__['writing_grade'].__get__(exam, Exam)) - - -# Example 11 -class Grade(object): - def __init__(self): - self._value = 0 - - def __get__(self, instance, instance_type): - return self._value - - def __set__(self, instance, value): - if not (0 <= value <= 100): - raise ValueError('Grade must be between 0 and 100') - self._value = value - -class Exam(object): - math_grade = Grade() - writing_grade = Grade() - science_grade = Grade() - - -# Example 12 -first_exam = Exam() -first_exam.writing_grade = 82 -first_exam.science_grade = 99 -print('Writing', first_exam.writing_grade) -print('Science', first_exam.science_grade) - - -# Example 13 -second_exam = Exam() -second_exam.writing_grade = 75 -print('Second', second_exam.writing_grade, 'is right') -print('First ', first_exam.writing_grade, 'is wrong') - - -# Example 14 -class Grade(object): - def __init__(self): - self._values = {} - - def __get__(self, instance, instance_type): - if instance is None: return self - return self._values.get(instance, 0) - - def __set__(self, instance, value): - if not (0 <= value <= 100): - raise ValueError('Grade must be between 0 and 100') - self._values[instance] = value - - -# Example 15 -from weakref import WeakKeyDictionary - -class Grade(object): - def __init__(self): - self._values = WeakKeyDictionary() - def __get__(self, instance, instance_type): - if instance is None: return self - return self._values.get(instance, 0) - - def __set__(self, instance, value): - if not (0 <= value <= 100): - raise ValueError('Grade must be between 0 and 100') - self._values[instance] = value - - -# Example 16 -class Exam(object): - math_grade = Grade() - writing_grade = Grade() - science_grade = Grade() - -first_exam = Exam() -first_exam.writing_grade = 82 -second_exam = Exam() -second_exam.writing_grade = 75 -print('First ', first_exam.writing_grade, 'is right') -print('Second', second_exam.writing_grade, 'is right') diff --git a/example_code/item_32.py b/example_code/item_32.py deleted file mode 100755 index 49837d3..0000000 --- a/example_code/item_32.py +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class LazyDB(object): - def __init__(self): - self.exists = 5 - - def __getattr__(self, name): - value = 'Value for %s' % name - setattr(self, name, value) - return value - - -# Example 2 -data = LazyDB() -print('Before:', data.__dict__) -print('foo: ', data.foo) -print('After: ', data.__dict__) - - -# Example 3 -class LoggingLazyDB(LazyDB): - def __getattr__(self, name): - print('Called __getattr__(%s)' % name) - return super().__getattr__(name) - -data = LoggingLazyDB() -print('exists:', data.exists) -print('foo: ', data.foo) -print('foo: ', data.foo) - - -# Example 4 -class ValidatingDB(object): - def __init__(self): - self.exists = 5 - - def __getattribute__(self, name): - print('Called __getattribute__(%s)' % name) - try: - return super().__getattribute__(name) - except AttributeError: - value = 'Value for %s' % name - setattr(self, name, value) - return value - -data = ValidatingDB() -print('exists:', data.exists) -print('foo: ', data.foo) -print('foo: ', data.foo) - - -# Example 5 -try: - class MissingPropertyDB(object): - def __getattr__(self, name): - if name == 'bad_name': - raise AttributeError('%s is missing' % name) - value = 'Value for %s' % name - setattr(self, name, value) - return value - - data = MissingPropertyDB() - data.foo # Test this works - data.bad_name -except: - logging.exception('Expected') -else: - assert False - - -# Example 6 -data = LoggingLazyDB() -print('Before: ', data.__dict__) -print('foo exists: ', hasattr(data, 'foo')) -print('After: ', data.__dict__) -print('foo exists: ', hasattr(data, 'foo')) - - -# Example 7 -data = ValidatingDB() -print('foo exists: ', hasattr(data, 'foo')) -print('foo exists: ', hasattr(data, 'foo')) - - -# Example 8 -class SavingDB(object): - def __setattr__(self, name, value): - # Save some data to the DB log - super().__setattr__(name, value) - - -# Example 9 -class LoggingSavingDB(SavingDB): - def __setattr__(self, name, value): - print('Called __setattr__(%s, %r)' % (name, value)) - super().__setattr__(name, value) - -data = LoggingSavingDB() -print('Before: ', data.__dict__) -data.foo = 5 -print('After: ', data.__dict__) -data.foo = 7 -print('Finally:', data.__dict__) - - -# Example 10 -class BrokenDictionaryDB(object): - def __init__(self, data): - self._data = data - - def __getattribute__(self, name): - print('Called __getattribute__(%s)' % name) - return self._data[name] - - -# Example 11 -try: - data = BrokenDictionaryDB({'foo': 3}) - data.foo -except: - logging.exception('Expected') -else: - assert False - - -# Example 12 -class DictionaryDB(object): - def __init__(self, data): - self._data = data - - def __getattribute__(self, name): - data_dict = super().__getattribute__('_data') - return data_dict[name] - -data = DictionaryDB({'foo': 3}) -print(data.foo) diff --git a/example_code/item_33.py b/example_code/item_33.py deleted file mode 100755 index 90293a2..0000000 --- a/example_code/item_33.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class Meta(type): - def __new__(meta, name, bases, class_dict): - orig_print = __builtins__.print - print = pprint - print((meta, name, bases, class_dict)) - print = orig_print - return type.__new__(meta, name, bases, class_dict) - -class MyClass(object, metaclass=Meta): - stuff = 123 - - def foo(self): - pass - - -# Example 3 -class ValidatePolygon(type): - def __new__(meta, name, bases, class_dict): - # Don't validate the abstract Polygon class - if bases != (object,): - if class_dict['sides'] < 3: - raise ValueError('Polygons need 3+ sides') - return type.__new__(meta, name, bases, class_dict) - -class Polygon(object, metaclass=ValidatePolygon): - sides = None # Specified by subclasses - - @classmethod - def interior_angles(cls): - return (cls.sides - 2) * 180 - -class Triangle(Polygon): - sides = 3 - -print(Triangle.interior_angles()) - - -# Example 4 -try: - print('Before class') - class Line(Polygon): - print('Before sides') - sides = 1 - print('After sides') - print('After class') -except: - logging.exception('Expected') -else: - assert False diff --git a/example_code/item_34.py b/example_code/item_34.py deleted file mode 100755 index 792c69d..0000000 --- a/example_code/item_34.py +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -import json - -class Serializable(object): - def __init__(self, *args): - self.args = args - - def serialize(self): - return json.dumps({'args': self.args}) - - -# Example 2 -class Point2D(Serializable): - def __init__(self, x, y): - super().__init__(x, y) - self.x = x - self.y = y - - def __repr__(self): - return 'Point2D(%d, %d)' % (self.x, self.y) - -point = Point2D(5, 3) -print('Object: ', point) -print('Serialized:', point.serialize()) - - -# Example 3 -class Deserializable(Serializable): - @classmethod - def deserialize(cls, json_data): - params = json.loads(json_data) - return cls(*params['args']) - - -# Example 4 -class BetterPoint2D(Deserializable): - def __init__(self, x, y): - super().__init__(x, y) - self.x = x - self.y = y - - def __repr__(self): - return 'BetterPoint2D(%d, %d)' % (self.x, self.y) - -point = BetterPoint2D(5, 3) -print('Before: ', point) -data = point.serialize() -print('Serialized:', data) -after = BetterPoint2D.deserialize(data) -print('After: ', after) - - -# Example 5 -class BetterSerializable(object): - def __init__(self, *args): - self.args = args - - def serialize(self): - return json.dumps({ - 'class': self.__class__.__name__, - 'args': self.args, - }) - - def __repr__(self): - return '%s(%s)' % ( - self.__class__.__name__, - ', '.join(str(x) for x in self.args)) - - -# Example 6 -registry = {} - -def register_class(target_class): - registry[target_class.__name__] = target_class - -def deserialize(data): - params = json.loads(data) - name = params['class'] - target_class = registry[name] - return target_class(*params['args']) - - -# Example 7 -class EvenBetterPoint2D(BetterSerializable): - def __init__(self, x, y): - super().__init__(x, y) - self.x = x - self.y = y - -register_class(EvenBetterPoint2D) - - -# Example 8 -point = EvenBetterPoint2D(5, 3) -print('Before: ', point) -data = point.serialize() -print('Serialized:', data) -after = deserialize(data) -print('After: ', after) - - -# Example 9 -class Point3D(BetterSerializable): - def __init__(self, x, y, z): - super().__init__(x, y, z) - self.x = x - self.y = y - self.z = z - -# Forgot to call register_class! Whoops! - - -# Example 10 -try: - point = Point3D(5, 9, -4) - data = point.serialize() - deserialize(data) -except: - logging.exception('Expected') -else: - assert False - - -# Example 11 -class Meta(type): - def __new__(meta, name, bases, class_dict): - cls = type.__new__(meta, name, bases, class_dict) - register_class(cls) - return cls - -class RegisteredSerializable(BetterSerializable, metaclass=Meta): - pass - - -# Example 12 -class Vector3D(RegisteredSerializable): - def __init__(self, x, y, z): - super().__init__(x, y, z) - self.x, self.y, self.z = x, y, z - -v3 = Vector3D(10, -7, 3) -print('Before: ', v3) -data = v3.serialize() -print('Serialized:', data) -print('After: ', deserialize(data)) diff --git a/example_code/item_35.py b/example_code/item_35.py deleted file mode 100755 index 5553d20..0000000 --- a/example_code/item_35.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class Field(object): - def __init__(self, name): - self.name = name - self.internal_name = '_' + self.name - - def __get__(self, instance, instance_type): - if instance is None: return self - return getattr(instance, self.internal_name, '') - - def __set__(self, instance, value): - setattr(instance, self.internal_name, value) - - -# Example 2 -class Customer(object): - # Class attributes - first_name = Field('first_name') - last_name = Field('last_name') - prefix = Field('prefix') - suffix = Field('suffix') - - -# Example 3 -foo = Customer() -print('Before:', repr(foo.first_name), foo.__dict__) -foo.first_name = 'Euclid' -print('After: ', repr(foo.first_name), foo.__dict__) - - -# Example 4 -class Meta(type): - def __new__(meta, name, bases, class_dict): - for key, value in class_dict.items(): - if isinstance(value, Field): - value.name = key - value.internal_name = '_' + key - cls = type.__new__(meta, name, bases, class_dict) - return cls - - -# Example 5 -class DatabaseRow(object, metaclass=Meta): - pass - - -# Example 6 -class Field(object): - def __init__(self): - # These will be assigned by the metaclass. - self.name = None - self.internal_name = None - def __get__(self, instance, instance_type): - if instance is None: return self - return getattr(instance, self.internal_name, '') - - def __set__(self, instance, value): - setattr(instance, self.internal_name, value) - - -# Example 7 -class BetterCustomer(DatabaseRow): - first_name = Field() - last_name = Field() - prefix = Field() - suffix = Field() - - -# Example 8 -foo = BetterCustomer() -print('Before:', repr(foo.first_name), foo.__dict__) -foo.first_name = 'Euler' -print('After: ', repr(foo.first_name), foo.__dict__) diff --git a/example_code/item_36.py b/example_code/item_36.py deleted file mode 100755 index 892c416..0000000 --- a/example_code/item_36.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -import subprocess -proc = subprocess.Popen( - ['echo', 'Hello from the child!'], - stdout=subprocess.PIPE) -out, err = proc.communicate() -print(out.decode('utf-8')) - - -# Example 2 -from time import sleep, time -proc = subprocess.Popen(['sleep', '0.3']) -while proc.poll() is None: - print('Working...') - # Some time consuming work here - sleep(0.2) - -print('Exit status', proc.poll()) - - -# Example 3 -def run_sleep(period): - proc = subprocess.Popen(['sleep', str(period)]) - return proc - -start = time() -procs = [] -for _ in range(10): - proc = run_sleep(0.1) - procs.append(proc) - - -# Example 4 -for proc in procs: - proc.communicate() -end = time() -print('Finished in %.3f seconds' % (end - start)) - - -# Example 5 -import os - -def run_openssl(data): - env = os.environ.copy() - env['password'] = b'\xe24U\n\xd0Ql3S\x11' - proc = subprocess.Popen( - ['openssl', 'enc', '-des3', '-pass', 'env:password'], - env=env, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE) - proc.stdin.write(data) - proc.stdin.flush() # Ensure the child gets input - return proc - - -# Example 6 -import os -procs = [] -for _ in range(3): - data = os.urandom(10) - proc = run_openssl(data) - procs.append(proc) - - -# Example 7 -for proc in procs: - out, err = proc.communicate() - print(out[-10:]) - - -# Example 8 -def run_md5(input_stdin): - proc = subprocess.Popen( - ['md5'], - stdin=input_stdin, - stdout=subprocess.PIPE) - return proc - - -# Example 9 -input_procs = [] -hash_procs = [] -for _ in range(3): - data = os.urandom(10) - proc = run_openssl(data) - input_procs.append(proc) - hash_proc = run_md5(proc.stdout) - hash_procs.append(hash_proc) - - -# Example 10 -for proc in input_procs: - proc.communicate() -for proc in hash_procs: - out, err = proc.communicate() - print(out.strip()) - - -# Example 11 -proc = run_sleep(10) -try: - proc.communicate(timeout=0.1) -except subprocess.TimeoutExpired: - proc.terminate() - proc.wait() - -print('Exit status', proc.poll()) diff --git a/example_code/item_38.py b/example_code/item_38.py deleted file mode 100755 index e781d51..0000000 --- a/example_code/item_38.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -class Counter(object): - def __init__(self): - self.count = 0 - - def increment(self, offset): - self.count += offset - - -# Example 2 -def worker(sensor_index, how_many, counter): - # I have a barrier in here so the workers synchronize - # when they start counting, otherwise it's hard to get a race - # because the overhead of starting a thread is high. - BARRIER.wait() - for _ in range(how_many): - # Read from the sensor - counter.increment(1) - - -# Example 3 -from threading import Barrier, Thread -BARRIER = Barrier(5) -def run_threads(func, how_many, counter): - threads = [] - for i in range(5): - args = (i, how_many, counter) - thread = Thread(target=func, args=args) - threads.append(thread) - thread.start() - for thread in threads: - thread.join() - - -# Example 4 -how_many = 10**5 -counter = Counter() -run_threads(worker, how_many, counter) -print('Counter should be %d, found %d' % - (5 * how_many, counter.count)) - - -# Example 5 -offset = 5 -counter.count += offset - - -# Example 6 -value = getattr(counter, 'count') -result = value + offset -setattr(counter, 'count', result) - - -# Example 7 -# Running in Thread A -value_a = getattr(counter, 'count') -# Context switch to Thread B -value_b = getattr(counter, 'count') -result_b = value_b + 1 -setattr(counter, 'count', result_b) -# Context switch back to Thread A -result_a = value_a + 1 -setattr(counter, 'count', result_a) - - -# Example 8 -from threading import Lock - -class LockingCounter(object): - def __init__(self): - self.lock = Lock() - self.count = 0 - - def increment(self, offset): - with self.lock: - self.count += offset - - -# Example 9 -BARRIER = Barrier(5) -counter = LockingCounter() -run_threads(worker, how_many, counter) -print('Counter should be %d, found %d' % - (5 * how_many, counter.count)) diff --git a/example_code/item_39.py b/example_code/item_39.py deleted file mode 100755 index 3bcd1ac..0000000 --- a/example_code/item_39.py +++ /dev/null @@ -1,248 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def download(item): - return item - -def resize(item): - return item - -def upload(item): - return item - - -# Example 2 -from threading import Lock -from collections import deque - -class MyQueue(object): - def __init__(self): - self.items = deque() - self.lock = Lock() - - -# Example 3 - def put(self, item): - with self.lock: - self.items.append(item) - - -# Example 4 - def get(self): - with self.lock: - return self.items.popleft() - - -# Example 5 -from threading import Thread -from time import sleep - -class Worker(Thread): - def __init__(self, func, in_queue, out_queue): - super().__init__() - self.func = func - self.in_queue = in_queue - self.out_queue = out_queue - self.polled_count = 0 - self.work_done = 0 - - -# Example 6 - def run(self): - while True: - self.polled_count += 1 - try: - item = self.in_queue.get() - except IndexError: - sleep(0.01) # No work to do - except AttributeError: - # The magic exit signal - return - else: - result = self.func(item) - self.out_queue.put(result) - self.work_done += 1 - - -# Example 7 -download_queue = MyQueue() -resize_queue = MyQueue() -upload_queue = MyQueue() -done_queue = MyQueue() -threads = [ - Worker(download, download_queue, resize_queue), - Worker(resize, resize_queue, upload_queue), - Worker(upload, upload_queue, done_queue), -] - - -# Example 8 -for thread in threads: - thread.start() -for _ in range(1000): - download_queue.put(object()) - - -# Example 9 -import time -while len(done_queue.items) < 1000: - # Do something useful while waiting - time.sleep(0.1) -# Stop all the threads by causing an exception in their -# run methods. -for thread in threads: - thread.in_queue = None - - -# Example 10 -processed = len(done_queue.items) -polled = sum(t.polled_count for t in threads) -print('Processed', processed, 'items after polling', - polled, 'times') - - -# Example 11 -from queue import Queue -queue = Queue() - -def consumer(): - print('Consumer waiting') - queue.get() # Runs after put() below - print('Consumer done') - -thread = Thread(target=consumer) -thread.start() - - -# Example 12 -print('Producer putting') -queue.put(object()) # Runs before get() above -thread.join() -print('Producer done') - - -# Example 13 -queue = Queue(1) # Buffer size of 1 - -def consumer(): - time.sleep(0.1) # Wait - queue.get() # Runs second - print('Consumer got 1') - queue.get() # Runs fourth - print('Consumer got 2') - -thread = Thread(target=consumer) -thread.start() - - -# Example 14 -queue.put(object()) # Runs first -print('Producer put 1') -queue.put(object()) # Runs third -print('Producer put 2') -thread.join() -print('Producer done') - - -# Example 15 -in_queue = Queue() - -def consumer(): - print('Consumer waiting') - work = in_queue.get() # Done second - print('Consumer working') - # Doing work - print('Consumer done') - in_queue.task_done() # Done third - -Thread(target=consumer).start() - - -# Example 16 -in_queue.put(object()) # Done first -print('Producer waiting') -in_queue.join() # Done fourth -print('Producer done') - - -# Example 17 -class ClosableQueue(Queue): - SENTINEL = object() - - def close(self): - self.put(self.SENTINEL) - - -# Example 18 - def __iter__(self): - while True: - item = self.get() - try: - if item is self.SENTINEL: - return # Cause the thread to exit - yield item - finally: - self.task_done() - - -# Example 19 -class StoppableWorker(Thread): - def __init__(self, func, in_queue, out_queue): - super().__init__() - self.func = func - self.in_queue = in_queue - self.out_queue = out_queue - - def run(self): - for item in self.in_queue: - result = self.func(item) - self.out_queue.put(result) - - -# Example 20 -download_queue = ClosableQueue() -resize_queue = ClosableQueue() -upload_queue = ClosableQueue() -done_queue = ClosableQueue() -threads = [ - StoppableWorker(download, download_queue, resize_queue), - StoppableWorker(resize, resize_queue, upload_queue), - StoppableWorker(upload, upload_queue, done_queue), -] - - -# Example 21 -for thread in threads: - thread.start() -for _ in range(1000): - download_queue.put(object()) -download_queue.close() - - -# Example 22 -download_queue.join() -resize_queue.close() -resize_queue.join() -upload_queue.close() -upload_queue.join() -print(done_queue.qsize(), 'items finished') diff --git a/example_code/item_40.py b/example_code/item_40.py deleted file mode 100755 index a4c01a5..0000000 --- a/example_code/item_40.py +++ /dev/null @@ -1,255 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def my_coroutine(): - while True: - received = yield - print('Received:', received) - -it = my_coroutine() -next(it) # Prime the coroutine -it.send('First') -it.send('Second') - - -# Example 2 -def minimize(): - current = yield - while True: - value = yield current - current = min(value, current) - - -# Example 3 -it = minimize() -next(it) # Prime the generator -print(it.send(10)) -print(it.send(4)) -print(it.send(22)) -print(it.send(-1)) - - -# Example 4 -ALIVE = '*' -EMPTY = '-' - - -# Example 5 -from collections import namedtuple -Query = namedtuple('Query', ('y', 'x')) - - -# Example 6 -def count_neighbors(y, x): - n_ = yield Query(y + 1, x + 0) # North - ne = yield Query(y + 1, x + 1) # Northeast - # Define e_, se, s_, sw, w_, nw ... - e_ = yield Query(y + 0, x + 1) # East - se = yield Query(y - 1, x + 1) # Southeast - s_ = yield Query(y - 1, x + 0) # South - sw = yield Query(y - 1, x - 1) # Southwest - w_ = yield Query(y + 0, x - 1) # West - nw = yield Query(y + 1, x - 1) # Northwest - neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] - count = 0 - for state in neighbor_states: - if state == ALIVE: - count += 1 - return count - - -# Example 7 -it = count_neighbors(10, 5) -q1 = next(it) # Get the first query -print('First yield: ', q1) -q2 = it.send(ALIVE) # Send q1 state, get q2 -print('Second yield:', q2) -q3 = it.send(ALIVE) # Send q2 state, get q3 -print('...') -q4 = it.send(EMPTY) -q5 = it.send(EMPTY) -q6 = it.send(EMPTY) -q7 = it.send(EMPTY) -q8 = it.send(EMPTY) -try: - it.send(EMPTY) # Send q8 state, retrieve count -except StopIteration as e: - print('Count: ', e.value) # Value from return statement - - -# Example 8 -Transition = namedtuple('Transition', ('y', 'x', 'state')) - - -# Example 9 -def game_logic(state, neighbors): - pass - -def step_cell(y, x): - state = yield Query(y, x) - neighbors = yield from count_neighbors(y, x) - next_state = game_logic(state, neighbors) - yield Transition(y, x, next_state) - - -# Example 10 -def game_logic(state, neighbors): - if state == ALIVE: - if neighbors < 2: - return EMPTY # Die: Too few - elif neighbors > 3: - return EMPTY # Die: Too many - else: - if neighbors == 3: - return ALIVE # Regenerate - return state - - -# Example 11 -it = step_cell(10, 5) -q0 = next(it) # Initial location query -print('Me: ', q0) -q1 = it.send(ALIVE) # Send my status, get neighbor query -print('Q1: ', q1) -print('...') -q2 = it.send(ALIVE) -q3 = it.send(ALIVE) -q4 = it.send(ALIVE) -q5 = it.send(ALIVE) -q6 = it.send(EMPTY) -q7 = it.send(EMPTY) -q8 = it.send(EMPTY) -t1 = it.send(EMPTY) # Send for q8, get game decision -print('Outcome: ', t1) - - -# Example 12 -TICK = object() - -def simulate(height, width): - while True: - for y in range(height): - for x in range(width): - yield from step_cell(y, x) - yield TICK - - -# Example 13 -class Grid(object): - def __init__(self, height, width): - self.height = height - self.width = width - self.rows = [] - for _ in range(self.height): - self.rows.append([EMPTY] * self.width) - - def __str__(self): - output = '' - for row in self.rows: - for cell in row: - output += cell - output += '\n' - return output - - -# Example 14 - def query(self, y, x): - return self.rows[y % self.height][x % self.width] - - def assign(self, y, x, state): - self.rows[y % self.height][x % self.width] = state - - -# Example 15 -def live_a_generation(grid, sim): - progeny = Grid(grid.height, grid.width) - item = next(sim) - while item is not TICK: - if isinstance(item, Query): - state = grid.query(item.y, item.x) - item = sim.send(state) - else: # Must be a Transition - progeny.assign(item.y, item.x, item.state) - item = next(sim) - return progeny - - -# Example 16 -grid = Grid(5, 9) -grid.assign(0, 3, ALIVE) -grid.assign(1, 4, ALIVE) -grid.assign(2, 2, ALIVE) -grid.assign(2, 3, ALIVE) -grid.assign(2, 4, ALIVE) -print(grid) - - -# Example 17 -class ColumnPrinter(object): - def __init__(self): - self.columns = [] - - def append(self, data): - self.columns.append(data) - - def __str__(self): - row_count = 1 - for data in self.columns: - row_count = max(row_count, len(data.splitlines()) + 1) - rows = [''] * row_count - for j in range(row_count): - for i, data in enumerate(self.columns): - line = data.splitlines()[max(0, j - 1)] - if j == 0: - padding = ' ' * (len(line) // 2) - rows[j] += padding + str(i) + padding - else: - rows[j] += line - if (i + 1) < len(self.columns): - rows[j] += ' | ' - return '\n'.join(rows) - -columns = ColumnPrinter() -sim = simulate(grid.height, grid.width) -for i in range(5): - columns.append(str(grid)) - grid = live_a_generation(grid, sim) - -print(columns) - - -# Example 20 -# This is for the introductory diagram -grid = Grid(5, 5) -grid.assign(1, 1, ALIVE) -grid.assign(2, 2, ALIVE) -grid.assign(2, 3, ALIVE) -grid.assign(3, 3, ALIVE) - -columns = ColumnPrinter() -sim = simulate(grid.height, grid.width) -for i in range(5): - columns.append(str(grid)) - grid = live_a_generation(grid, sim) - -print(columns) diff --git a/example_code/item_40_example_19.py b/example_code/item_40_example_19.py deleted file mode 100755 index 78a754f..0000000 --- a/example_code/item_40_example_19.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 19 -class MyReturn(Exception): - def __init__(self, value): - self.value = value - -def delegated(): - yield 1 - raise MyReturn(2) # return 2 in Python 3 - yield 'Not reached' - -def composed(): - try: - for value in delegated(): - yield value - except MyReturn as e: - output = e.value - yield output * 4 - -print list(composed()) diff --git a/example_code/item_41.py b/example_code/item_41.py deleted file mode 100755 index 5acafde..0000000 --- a/example_code/item_41.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def gcd(pair): - a, b = pair - low = min(a, b) - for i in range(low, 0, -1): - if a % i == 0 and b % i == 0: - return i - - -# Example 2 -from time import time -numbers = [(1963309, 2265973), (2030677, 3814172), - (1551645, 2229620), (2039045, 2020802)] -start = time() -results = list(map(gcd, numbers)) -end = time() -print('Took %.3f seconds' % (end - start)) - - -# Example 3 -from concurrent.futures import ThreadPoolExecutor - -start = time() -pool = ThreadPoolExecutor(max_workers=2) -results = list(pool.map(gcd, numbers)) -end = time() -print('Took %.3f seconds' % (end - start)) - - -# Example 4 -from concurrent.futures import ProcessPoolExecutor - -start = time() -pool = ProcessPoolExecutor(max_workers=2) # The one change -results = list(pool.map(gcd, numbers)) -end = time() -print('Took %.3f seconds' % (end - start)) diff --git a/example_code/item_42.py b/example_code/item_42.py deleted file mode 100755 index 43395bb..0000000 --- a/example_code/item_42.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -def trace(func): - def wrapper(*args, **kwargs): - result = func(*args, **kwargs) - print('%s(%r, %r) -> %r' % - (func.__name__, args, kwargs, result)) - return result - return wrapper - - -# Example 2 -@trace -def fibonacci(n): - """Return the n-th Fibonacci number""" - if n in (0, 1): - return n - return (fibonacci(n - 2) + fibonacci(n - 1)) - - -# Example 3 -def fibonacci(n): - """Return the n-th Fibonacci number""" - if n in (0, 1): - return n - return (fibonacci(n - 2) + fibonacci(n - 1)) - -fibonacci = trace(fibonacci) - - -# Example 4 -fibonacci(3) - - -# Example 5 -print(fibonacci) - - -# Example 6 -try: - # Example of how pickle breaks - import pickle - - def my_func(): - return 1 - - # This will be okay - print(pickle.dumps(my_func)) - - @trace - def my_func2(): - return 2 - - # This will explode - print(pickle.dumps(my_func2)) -except: - logging.exception('Expected') -else: - assert False - - -# Example 7 -help(fibonacci) - - -# Example 8 -from functools import wraps -def trace(func): - @wraps(func) - def wrapper(*args, **kwargs): - result = func(*args, **kwargs) - print('%s(%r, %r) -> %r' % - (func.__name__, args, kwargs, result)) - return result - return wrapper - -@trace -def fibonacci(n): - """Return the n-th Fibonacci number""" - if n in (0, 1): - return n - return (fibonacci(n - 2) + - fibonacci(n - 1)) - - -# Example 9 -help(fibonacci) diff --git a/example_code/item_43.py b/example_code/item_43.py deleted file mode 100755 index 7e0410a..0000000 --- a/example_code/item_43.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -from threading import Lock -lock = Lock() -with lock: - print('Lock is held') - - -# Example 2 -lock.acquire() -try: - print('Lock is held') -finally: - lock.release() - - -# Example 3 -import logging -logging.getLogger().setLevel(logging.WARNING) -def my_function(): - logging.debug('Some debug data') - logging.error('Error log here') - logging.debug('More debug data') - - -# Example 4 -my_function() - - -# Example 5 -from contextlib import contextmanager -@contextmanager -def debug_logging(level): - logger = logging.getLogger() - old_level = logger.getEffectiveLevel() - logger.setLevel(level) - try: - yield - finally: - logger.setLevel(old_level) - - -# Example 6 -with debug_logging(logging.DEBUG): - print('Inside:') - my_function() -print('After:') -my_function() - - -# Example 7 -with open('my_output.txt', 'w') as handle: - handle.write('This is some data!') - - -# Example 8 -@contextmanager -def log_level(level, name): - logger = logging.getLogger(name) - old_level = logger.getEffectiveLevel() - logger.setLevel(level) - try: - yield logger - finally: - logger.setLevel(old_level) - - -# Example 9 -with log_level(logging.DEBUG, 'my-log') as logger: - logger.debug('This is my message!') - logging.debug('This will not print') - - -# Example 10 -logger = logging.getLogger('my-log') -logger.debug('Debug will not print') -logger.error('Error will print') diff --git a/example_code/item_45.py b/example_code/item_45.py deleted file mode 100755 index aa1cbf4..0000000 --- a/example_code/item_45.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -from time import localtime, strftime - -now = 1407694710 -local_tuple = localtime(now) -time_format = '%Y-%m-%d %H:%M:%S' -time_str = strftime(time_format, local_tuple) -print(time_str) - - -# Example 2 -from time import mktime, strptime - -time_tuple = strptime(time_str, time_format) -utc_now = mktime(time_tuple) -print(utc_now) - - -# Example 3 -parse_format = '%Y-%m-%d %H:%M:%S %Z' -depart_sfo = '2014-05-01 15:45:16 PDT' -time_tuple = strptime(depart_sfo, parse_format) -time_str = strftime(time_format, time_tuple) -print(time_str) - - -# Example 4 -try: - arrival_nyc = '2014-05-01 23:33:24 EDT' - time_tuple = strptime(arrival_nyc, time_format) -except: - logging.exception('Expected') -else: - assert False - - -# Example 5 -from datetime import datetime, timezone - -now = datetime(2014, 8, 10, 18, 18, 30) -now_utc = now.replace(tzinfo=timezone.utc) -now_local = now_utc.astimezone() -print(now_local) - - -# Example 6 -time_str = '2014-08-10 11:18:30' -now = datetime.strptime(time_str, time_format) -time_tuple = now.timetuple() -utc_now = mktime(time_tuple) -print(utc_now) - - -# Example 7 -import pytz -arrival_nyc = '2014-05-01 23:33:24' -nyc_dt_naive = datetime.strptime(arrival_nyc, time_format) -eastern = pytz.timezone('US/Eastern') -nyc_dt = eastern.localize(nyc_dt_naive) -utc_dt = pytz.utc.normalize(nyc_dt.astimezone(pytz.utc)) -print(utc_dt) - - -# Example 8 -pacific = pytz.timezone('US/Pacific') -sf_dt = pacific.normalize(utc_dt.astimezone(pacific)) -print(sf_dt) - - -# Example 9 -nepal = pytz.timezone('Asia/Katmandu') -nepal_dt = nepal.normalize(utc_dt.astimezone(nepal)) -print(nepal_dt) diff --git a/example_code/item_46.py b/example_code/item_46.py deleted file mode 100755 index a937260..0000000 --- a/example_code/item_46.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -from collections import deque -fifo = deque() -fifo.append(1) # Producer -fifo.append(2) -fifo.append(3) -x = fifo.popleft() # Consumer -print(x) - - -# Example 2 -a = {} -a['foo'] = 1 -a['bar'] = 2 -from random import randint - -# Randomly populate 'b' to cause hash conflicts -while True: - z = randint(99, 1013) - b = {} - for i in range(z): - b[i] = i - b['foo'] = 1 - b['bar'] = 2 - for i in range(z): - del b[i] - if str(b) != str(a): - break - -print(a) -print(b) -print('Equal?', a == b) - - -# Example 3 -from collections import OrderedDict -a = OrderedDict() -a['foo'] = 1 -a['bar'] = 2 - -b = OrderedDict() -b['foo'] = 'red' -b['bar'] = 'blue' - -for value1, value2 in zip(a.values(), b.values()): - print(value1, value2) - - -# Example 4 -stats = {} -key = 'my_counter' -if key not in stats: - stats[key] = 0 -stats[key] += 1 -print(stats) - - -# Example 5 -from collections import defaultdict -stats = defaultdict(int) -stats['my_counter'] += 1 -print(dict(stats)) - - -# Example 6 -from heapq import * -a = [] -heappush(a, 5) -heappush(a, 3) -heappush(a, 7) -heappush(a, 4) - - -# Example 7 -print(heappop(a), heappop(a), heappop(a), heappop(a)) - - -# Example 8 -a = [] -heappush(a, 5) -heappush(a, 3) -heappush(a, 7) -heappush(a, 4) -assert a[0] == nsmallest(1, a)[0] == 3 - - -# Example 9 -print('Before:', a) -a.sort() -print('After: ', a) - - -# Example 10 -x = list(range(10**6)) -i = x.index(991234) -print(i) - - -# Example 11 -from bisect import bisect_left -i = bisect_left(x, 991234) -print(i) - - -# Example 12 -from timeit import timeit -print(timeit( - 'a.index(len(a)-1)', - 'a = list(range(100))', - number=1000)) -print(timeit( - 'bisect_left(a, len(a)-1)', - 'from bisect import bisect_left;' - 'a = list(range(10**6))', - number=1000)) diff --git a/example_code/item_47.py b/example_code/item_47.py deleted file mode 100755 index 7d7ce92..0000000 --- a/example_code/item_47.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -rate = 1.45 -seconds = 3*60 + 42 -cost = rate * seconds / 60 -print(cost) - - -# Example 2 -print(round(cost, 2)) - - -# Example 3 -rate = 0.05 -seconds = 5 -cost = rate * seconds / 60 -print(cost) - - -# Example 4 -print(round(cost, 2)) - - -# Example 5 -from decimal import Decimal -from decimal import ROUND_UP -rate = Decimal('1.45') -seconds = Decimal('222') # 3*60 + 42 -cost = rate * seconds / Decimal('60') -print(cost) - - -# Example 6 -rounded = cost.quantize(Decimal('0.01'), rounding=ROUND_UP) -print(rounded) - - -# Example 7 -rate = Decimal('0.05') -seconds = Decimal('5') -cost = rate * seconds / Decimal('60') -print(cost) - - -# Example 8 -rounded = cost.quantize(Decimal('0.01'), rounding=ROUND_UP) -print(rounded) diff --git a/example_code/item_51.py b/example_code/item_51.py deleted file mode 100755 index a911fbe..0000000 --- a/example_code/item_51.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -try: - def determine_weight(volume, density): - if density <= 0: - raise ValueError('Density must be positive') - - determine_weight(1, 0) -except: - logging.exception('Expected') -else: - assert False - - -# Example 2 -# my_module.py -class Error(Exception): - """Base-class for all exceptions raised by this module.""" - -class InvalidDensityError(Error): - """There was a problem with a provided density value.""" - - -# Example 3 -class my_module(object): - Error = Error - InvalidDensityError = InvalidDensityError - - @staticmethod - def determine_weight(volume, density): - if density <= 0: - raise InvalidDensityError('Density must be positive') -try: - weight = my_module.determine_weight(1, -1) - assert False -except my_module.Error as e: - logging.error('Unexpected error: %s', e) - - -# Example 4 -weight = 5 -try: - weight = my_module.determine_weight(1, -1) - assert False -except my_module.InvalidDensityError: - weight = 0 -except my_module.Error as e: - logging.error('Bug in the calling code: %s', e) - -assert weight == 0 - - -# Example 5 -weight = 5 -try: - weight = my_module.determine_weight(1, -1) - assert False -except my_module.InvalidDensityError: - weight = 0 -except my_module.Error as e: - logging.error('Bug in the calling code: %s', e) -except Exception as e: - logging.error('Bug in the API code: %s', e) - raise - -assert weight == 0 - - -# Example 6 -# my_module.py -class NegativeDensityError(InvalidDensityError): - """A provided density value was negative.""" - -def determine_weight(volume, density): - if density < 0: - raise NegativeDensityError - - -# Example 7 -try: - my_module.NegativeDensityError = NegativeDensityError - my_module.determine_weight = determine_weight - try: - weight = my_module.determine_weight(1, -1) - assert False - except my_module.NegativeDensityError as e: - raise ValueError('Must supply non-negative density') from e - except my_module.InvalidDensityError: - weight = 0 - except my_module.Error as e: - logging.error('Bug in the calling code: %s', e) - except Exception as e: - logging.error('Bug in the API code: %s', e) - raise -except: - logging.exception('Expected') -else: - assert False - - -# Example 8 -# my_module.py -class WeightError(Error): - """Base-class for weight calculation errors.""" - -class VolumeError(Error): - """Base-class for volume calculation errors.""" - -class DensityError(Error): - """Base-class for density calculation errors.""" diff --git a/example_code/item_55.py b/example_code/item_55.py deleted file mode 100755 index bb5f9e1..0000000 --- a/example_code/item_55.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2014 Brett Slatkin, Pearson Education Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Preamble to mimick book environment -import logging -from pprint import pprint -from sys import stdout as STDOUT - - -# Example 1 -print('foo bar') - - -# Example 2 -print('%s' % 'foo bar') - - -# Example 3 -print(5) -print('5') - - -# Example 4 -a = '\x07' -print(repr(a)) - - -# Example 5 -b = eval(repr(a)) -assert a == b - - -# Example 6 -print(repr(5)) -print(repr('5')) - - -# Example 7 -print('%r' % 5) -print('%r' % '5') - - -# Example 8 -class OpaqueClass(object): - def __init__(self, x, y): - self.x = x - self.y = y - -obj = OpaqueClass(1, 2) -print(obj) - - -# Example 9 -class BetterClass(object): - def __init__(self, x, y): - self.x = 1 - self.y = 2 - def __repr__(self): - return 'BetterClass(%d, %d)' % (self.x, self.y) - - -# Example 10 -obj = BetterClass(1, 2) -print(obj) - - -# Example 11 -obj = OpaqueClass(4, 5) -print(obj.__dict__)