├── .gitattributes ├── Chapter10 ├── 10_02_simple_socket.py ├── 10_03_simple_client.py ├── 10_04_logging_decorator.py ├── 10_05_gzip_decorator.py ├── 10_06_calling_decorated_sockets.py ├── 10_09_logging_decorator.py ├── 10_10_decorator_syntax.py ├── 10_11_observer_core.py ├── 10_12_observer_observing.py ├── 10_13_strategy_tile.py ├── 10_14_simple_xml_to_parse.xml ├── 10_15_xml_states.py ├── 10_16_singleton_using_new.py ├── 10_17_xml_singletonstates.py ├── 10_18_create_database_for_template.py ├── 10_19_template_abstract_noimple.py ├── 10_20_template_abstract_implemented.py ├── 10_21_template_concretes.py ├── Benson.jpg ├── TILED.jpg └── sales.db ├── Chapter11 ├── 11_01_age_calculator.py ├── 11_02_age_calculator_adapted.py ├── 11_03_age_calculator_adapt_date.py ├── 11_04_email_facade.py ├── 11_05_flyweight_factory.py ├── 11_06_flyweight_init.py ├── 11_07_flyweight_check_serial.py ├── 11_08_car_class.py ├── 11_09_window_commands.py ├── 11_10_window_command_invokers.py ├── 11_11_window_command_commands.py ├── 11_12_window_command_function.py ├── 11_13_document_command_callable.py ├── 11_14_formatters.py ├── 11_15_formatter_factories.py ├── 11_16_composite_folder_methods.py ├── 11_17_component_hierarchy.py └── 11_18_add_child.py ├── Chapter12 ├── 12_01_simplest_unittest.py ├── 12_02_assertraises_python.py ├── 12_03_stats.py ├── 12_04_test_stats.py ├── 12_05_skipping_tests.py ├── 12_06_simplestpytest.py ├── 12_06_simplestpytest.pyc ├── 12_07_class_pytest.py ├── 12_07_class_pytest.pyc ├── 12_08_setup_teardown.py ├── 12_08_setup_teardown.pyc ├── 12_09_funcargs.py ├── 12_09_funcargs.pyc ├── 12_10_funcarg_finalizer.py ├── 12_10_funcarg_finalizer.pyc ├── 12_11_echo_server.py ├── 12_12_pytest_echo.py ├── 12_12_pytest_echo.pyc ├── 12_13_pytest_simple_skip.py ├── 12_13_pytest_simple_skip.pyc ├── 12_14_pytest_importorskip.py ├── 12_14_pytest_importorskip.pyc ├── 12_15_pytest_skipifmark.py ├── 12_15_pytest_skipifmark.pyc ├── 12_16_mock_flightstatus.py ├── 12_17_mock_redis.py ├── 12_30_coverage_unittest.py ├── __pycache__ │ ├── stats.cpython-34.pyc │ └── test_mock.cpython-34-PYTEST.pyc ├── casestudy │ ├── __pycache__ │ │ ├── test_vigenere_cipher.cpython-34-PYTEST.pyc │ │ └── vigenere_cipher.cpython-34.pyc │ ├── test_vigenere_cipher.py │ ├── test_vigenere_cipher.pyc │ ├── vigenere_cipher.py │ ├── vigenere_cipher.pyc │ └── vigenere_cipher1.py ├── dump.rdb ├── stats.py ├── stats.pyc └── test_mock.py ├── Chapter13 ├── 13_01_two_basic_threads.py ├── 13_02_thread_wait.py ├── 13_03_parallel.py ├── 13_04_prime_factor.py ├── 13_05_post_search.py ├── 13_06_futures.py ├── 13_07_basic_async.py ├── 13_08_asyncio_dns.py ├── 13_09_async_multiprocessing.py ├── 13_10_async_client.py └── case_study │ ├── big.bmp │ ├── big2.bmp │ ├── big3.bmp │ ├── big4.bmp │ ├── big5.bmp │ ├── big6.bmp │ ├── big7.bmp │ ├── big8.bmp │ ├── bricks.bmp │ ├── compress_bmp.py │ ├── decompress_to_bmp.py │ └── row.bmp ├── Chapter3 ├── 3_01_inheriting_from_object.py ├── 3_02_simple_contact_class_to_inherit_from.py ├── 3_03_contact_inherit_supplier.py ├── 3_04_contact_list_inheritance.py ├── 3_05_dictionary_long_name.py ├── 3_06_friend_overrides_init.py ├── 3_07_friend_overrides_init_super.py ├── 3_08_send_mail.py ├── 3_09_send_mail_multi.py ├── 3_10_friend_address_holder.py ├── 3_11_friend_multi.py ├── 3_12_contrived_diamond.py ├── 3_14_contrived_diamond_super.py ├── 3_15_friend_multi_super.py ├── 3_16_polymorphic_audio.py ├── 3_17.1_abc_container.py ├── 3_17.2_abc_media.py ├── 3_17_ducktype_flac.py ├── 3_18_property.py ├── 3_19_apartment_ugly_prompt.py ├── 3_20_validation_function.py ├── 3_21_apartment_nice_prompt.py ├── 3_22_house.py ├── 3_23_purchase_and_rental.py ├── 3_24_house_rental.py ├── 3_25_remaining_subclasses.py ├── 3_26_rudimentary_agent.py ├── 3_27_type_map.py ├── 3_28_add_property.py └── final_case_study.py ├── Chapter4 ├── 04_03_method_calls_excepting.py ├── 4_01_even_integers.py ├── 4_02_exception_quits.py ├── 4_04_try_except.py ├── 4_05_catch_specific_exception.py ├── 4_06_catch_multiple_exceptions.py ├── 4_07_catch_multiple_different.py ├── 4_08_catch_as_keyword.py ├── 4_09_finally_and_else.py ├── 4_10_defining_an_exception.py ├── 4_11_exception_with_custom_args.py ├── 4_12_handle_custom_exception.py ├── 4_13_branching_vs_exceptions.py ├── 4_14_inventory_mock_object.py ├── 4_15_inventory_handling.py ├── 4_16_auth_user.py ├── 4_17_authenticator.py ├── 4_18_login.py ├── 4_19_authorizor.py ├── 4_20_test_auth.py ├── __pycache__ │ └── auth.cpython-34.pyc ├── auth.py └── auth.pyc ├── Chapter5 ├── 5_01_distances_no_objects.py ├── 5_02_distances_by_object.py ├── 5_03_object_polygon_init.py ├── 5_04_pytho_ugly_as_java.py ├── 5_05_python_pretty_as_python.py ├── 5_06_setting_name_in_method.py ├── 5_07_setting_name_property.py ├── 5_08_property_arguments.py ├── 5_09_property_decorator_get.py ├── 5_11_property_decorator_arguments.py ├── 5_12_property_decorator_get_set.py ├── 5_13_read_only_setattr.py ├── 5_14_read_only_getattribute.py ├── 5_15_cache_getter.py ├── 5_16_average_property.py ├── 5_17_zipsearch.py ├── 5_18_zipprocessor.py ├── 5_19_zipreplace_inheritance.py ├── 5_20_scaleimage_inheritance.py ├── 5_24_most_basic_document.py ├── 5_25_document_cursor.py ├── 5_26_document_using_cursor.py ├── 5_27_string_property.py ├── 5_28_Character_class.py ├── 5_29_document_with_character.py ├── Document.py ├── zip_processor.py └── zip_processor.pyc ├── Chapter6 ├── 6_01_empty_object.py ├── 6_02_pass_tuple_to_function.py ├── 6_03_named_tuple.py ├── 6_04_dict_stocks.py ├── 6_05_random_key_dict.py ├── 6_06_setdefault_frequency.py ├── 6_07_defaultdict_frequency.py ├── 6_08_defaultdict_custom_function.py ├── 6_09_counter_frequency.py ├── 6_09_counter_poll.py ├── 6_09_list_tuple_frequency.py ├── 6_10_object_comparison.py ├── 6_11_song_artist_set.py ├── 6_12_set_operations.py ├── 6_13_set_operations2.py ├── 6_14_oop_pairs.py ├── 6_15_stupid_adding_integer.py ├── 6_16_dictsorted.py ├── 6_17_link_parser.py ├── 6_18_normalize_url.py ├── 6_19_visited_links_sets.py ├── 6_20_collect_remaining_links.py ├── 6_21_print_collected_links.py ├── 6_22_dict_link_collector.py ├── 6_23_queue_link_collector.py ├── case_study_serve │ ├── blog.html │ ├── contact.html │ ├── esme.html │ ├── hobbies.html │ ├── index.html │ └── yoga.html └── link_collector.py ├── Chapter7 ├── 7_01_reversible_objects.py ├── 7_02_enumerate_line_numbers.py ├── 7_03.1_zip_to_enumerate.py ├── 7_03_enumerate_max_min.py ├── 7_04_tdf_contact.txt ├── 7_05_read_file.py ├── 7_05_tdf_processor.py ├── 7_06_write.py ├── 7_07_with.py ├── 7_08_context_manager.py ├── 7_18_bad_kw_default.py ├── 7_19_link_downloader.py ├── 7_20_link_downloader_vararg.py ├── 7_21_kwarg_options.py ├── 7_22_all_arguments.py ├── 7_23_unpacking_arguments.py ├── 7_24_function_object.py ├── 7_25_timer.py ├── 7_26_timer_test.py ├── 7_27_add_function_to_object.py ├── 7_28_callable_repeat.py ├── 7_29_send_email.py ├── 7_30_send_email_dict_headers.py ├── 7_32_mailing_list_defaultdict_set.py ├── 7_33_mailing_list_get_emails.py ├── 7_34_send_mailing.py ├── 7_35_load_save.py ├── 7_36_enter_exit.py ├── __pycache__ │ └── timer.cpython-34.pyc ├── addresses.db ├── mailing_list.py ├── timer.py └── timer.pyc ├── Chapter8 ├── 8_01_string_creation.py ├── 8_02_format_empty.py ├── 8_03_format_position.py ├── 8_04_format_some_positions_broken.py ├── 8_05_brace_escape.py ├── 8_06_format_kw_args.py ├── 8_07_unlabelled_kw.py ├── 8_08_tuple_dict_format.py ├── 8_09_tuple_in_dict_format.py ├── 8_10_object_formatting.py ├── 8_11_no_format.py ├── 8_12_currency_format.py ├── 8_13_tabular.py ├── 8_14_format_datetime.py ├── 8_15_encode_bytes.py ├── 8_16_decode_unicode.py ├── 8_17_bytearray_replace.py ├── 8_18_bytearray_index.py ├── 8_23.1_basic_regex.py ├── 8_23.2_regex_generic.py ├── 8_23.3_match_group.py ├── 8_23_stringio.py ├── 8_24_basic_pickling.py ├── 8_25_state_pickling.py ├── 8_26_json_objects.py ├── 8_27_template_boilerplate.py ├── 8_28_template_process.py ├── 8_29_template_processer_complete.py └── case_study_input │ ├── context.json │ ├── footer.html │ ├── header.html │ ├── main.html │ └── menu.html ├── Chapter9 ├── 9_01_iterator.py ├── 9_06_for_loop_converter.py ├── 9_07_list_comp_converter.py ├── 9_08_list_comp_exclude.py ├── 9_09_tdf_list_comp.py ├── 9_10_set_comprehension.py ├── 9_11_dict_comprehension.py ├── 9_12_log_file.log ├── 9_13_log_processor.py ├── 9_14_log_delete_warning_expression.py ├── 9_15_log_delete_warnings_loop.py ├── 9_16_log_delete_warnings_object.py ├── 9_17_log_delete_warnings_generator.py ├── 9_18_log_delete_warnings_yield_from.py ├── 9_19_yield_from_filesystem.py ├── 9_20_basic_count_coroutine.py ├── 9_21_kernel_log.py ├── 9_22_load_dataset.py ├── 9_23_generate_colors.py ├── 9_24_color_distance.py ├── 9_25_knearest.py ├── 9_26_write_results.py ├── EXAMPLE_LOG.log ├── case_study_machine_learn.py ├── colors.csv ├── example.log ├── kivy_color_checker │ ├── colorchecker.kv │ ├── main.py │ └── output.csv ├── kivy_color_classifier │ ├── colorclassifier.kv │ └── main.py └── output.csv ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.bmp filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /Chapter10/10_02_simple_socket.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | def respond(client): 4 | response = input("Enter a value: ") 5 | client.send(bytes(response, 'utf8')) 6 | client.close() 7 | 8 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 9 | server.bind(('localhost',2401)) 10 | server.listen(1) 11 | try: 12 | while True: 13 | client, addr = server.accept() 14 | respond(client) 15 | finally: 16 | server.close() 17 | -------------------------------------------------------------------------------- /Chapter10/10_03_simple_client.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 4 | client.connect(('localhost', 2401)) 5 | print("Received: {0}".format(client.recv(1024))) 6 | client.close() 7 | 8 | -------------------------------------------------------------------------------- /Chapter10/10_04_logging_decorator.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | class LogSocket: 4 | def __init__(self, socket): 5 | self.socket = socket 6 | 7 | def send(self, data): 8 | print("Sending {0} to {1}".format( 9 | data, self.socket.getpeername()[0])) 10 | self.socket.send(data) 11 | 12 | def close(self): 13 | self.socket.close() 14 | 15 | def respond(client): 16 | response = input("Enter a value: ") 17 | client.send(bytes(response, 'utf8')) 18 | client.close() 19 | 20 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 21 | server.bind(('',2401)) 22 | server.listen(1) 23 | try: 24 | while True: 25 | client, addr = server.accept() 26 | respond(LogSocket(client)) 27 | finally: 28 | server.close() 29 | -------------------------------------------------------------------------------- /Chapter10/10_05_gzip_decorator.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import gzip 3 | from io import BytesIO 4 | 5 | class GzipSocket: 6 | def __init__(self, socket): 7 | self.socket = socket 8 | 9 | def send(self, data): 10 | buf = BytesIO() 11 | zipfile = gzip.GzipFile(fileobj=buf, mode="w") 12 | zipfile.write(data) 13 | zipfile.close() 14 | self.socket.send(buf.getvalue()) 15 | 16 | def close(self): 17 | self.socket.close() 18 | 19 | def respond(client): 20 | response = input("Enter a value: ") 21 | client.send(bytes(response, 'utf8')) 22 | client.close() 23 | 24 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 25 | server.bind(('',2401)) 26 | server.listen(1) 27 | try: 28 | while True: 29 | client, addr = server.accept() 30 | respond(GzipSocket(client)) 31 | finally: 32 | server.close() 33 | -------------------------------------------------------------------------------- /Chapter10/10_06_calling_decorated_sockets.py: -------------------------------------------------------------------------------- 1 | client, addr = server.accept() 2 | if log_send: 3 | client = LoggingSocket(client) 4 | if client.getpeername()[0] in compress_hosts: 5 | client = GzipSocket(client) 6 | respond(client) 7 | -------------------------------------------------------------------------------- /Chapter10/10_09_logging_decorator.py: -------------------------------------------------------------------------------- 1 | import time 2 | def log_calls(func): 3 | def wrapper(*args, **kwargs): 4 | now = time.time() 5 | print("Calling {0} with {1} and {2}".format( 6 | func.__name__, args, kwargs)) 7 | return_value = func(*args, **kwargs) 8 | print("Executed {0} in {1}ms".format( 9 | func.__name__, time.time() - now)) 10 | return return_value 11 | return wrapper 12 | 13 | def test1(a,b,c): 14 | print("\ttest1 called") 15 | 16 | def test2(a,b): 17 | print("\ttest2 called") 18 | 19 | def test3(a,b): 20 | print("\ttest3 called") 21 | time.sleep(1) 22 | 23 | test1 = log_calls(test1) 24 | test2 = log_calls(test2) 25 | test3 = log_calls(test3) 26 | 27 | test1(1,2,3) 28 | test2(4,b=5) 29 | test3(6,7) 30 | -------------------------------------------------------------------------------- /Chapter10/10_10_decorator_syntax.py: -------------------------------------------------------------------------------- 1 | @log_calls 2 | def test1(a,b,c): 3 | print("\ttest1 called") 4 | -------------------------------------------------------------------------------- /Chapter10/10_11_observer_core.py: -------------------------------------------------------------------------------- 1 | class Inventory: 2 | def __init__(self): 3 | self.observers = [] 4 | self._product = None 5 | self._quantity = 0 6 | 7 | def attach(self, observer): 8 | self.observers.append(observer) 9 | 10 | @property 11 | def product(self): 12 | return self._product 13 | @product.setter 14 | def product(self, value): 15 | self._product = value 16 | self._update_observers() 17 | 18 | @property 19 | def quantity(self): 20 | return self._quantity 21 | @quantity.setter 22 | def quantity(self, value): 23 | self._quantity = value 24 | self._update_observers() 25 | 26 | def _update_observers(self): 27 | for observer in self.observers: 28 | observer() 29 | -------------------------------------------------------------------------------- /Chapter10/10_12_observer_observing.py: -------------------------------------------------------------------------------- 1 | class Inventory: 2 | def __init__(self): 3 | self.observers = [] 4 | self._product = None 5 | self._quantity = 0 6 | 7 | def attach(self, observer): 8 | self.observers.append(observer) 9 | 10 | @property 11 | def product(self): 12 | return self._product 13 | @product.setter 14 | def product(self, value): 15 | self._product = value 16 | self._update_observers() 17 | 18 | @property 19 | def quantity(self): 20 | return self._quantity 21 | @quantity.setter 22 | def quantity(self, value): 23 | self._quantity = value 24 | self._update_observers() 25 | 26 | def _update_observers(self): 27 | for observer in self.observers: 28 | observer() 29 | 30 | class ConsoleObserver: 31 | def __init__(self, inventory): 32 | self.inventory = inventory 33 | 34 | def __call__(self): 35 | print(self.inventory.product) 36 | print(self.inventory.quantity) 37 | -------------------------------------------------------------------------------- /Chapter10/10_13_strategy_tile.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | 3 | 4 | class TiledStrategy: 5 | def make_background(self, img_file, desktop_size): 6 | in_img = Image.open(img_file) 7 | out_img = Image.new('RGB', desktop_size) 8 | num_tiles = [ 9 | o // i + 1 for o, i in 10 | zip(out_img.size, in_img.size) 11 | ] 12 | for x in range(num_tiles[0]): 13 | for y in range(num_tiles[1]): 14 | out_img.paste( 15 | in_img, 16 | ( 17 | in_img.size[0] * x, 18 | in_img.size[1] * y, 19 | in_img.size[0] * (x+1), 20 | in_img.size[1] * (y+1) 21 | ) 22 | ) 23 | return out_img 24 | 25 | 26 | class CenteredStrategy: 27 | def make_background(self, img_file, desktop_size): 28 | in_img = Image.open(img_file) 29 | out_img = Image.new('RGB', desktop_size) 30 | left = (out_img.size[0] - in_img.size[0]) // 2 31 | top = (out_img.size[1] - in_img.size[1]) // 2 32 | out_img.paste( 33 | in_img, 34 | ( 35 | left, 36 | top, 37 | left+in_img.size[0], 38 | top + in_img.size[1] 39 | ) 40 | ) 41 | return out_img 42 | 43 | 44 | class ScaledStrategy: 45 | def make_background(self, img_file, desktop_size): 46 | in_img = Image.open(img_file) 47 | out_img = in_img.resize(desktop_size) 48 | return out_img 49 | -------------------------------------------------------------------------------- /Chapter10/10_14_simple_xml_to_parse.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dusty Phillips 4 | Packt Publishing 5 | Python 3 Object Oriented Programming 6 | 7 | 8 | 1 9 | Object Oriented Design 10 | 11 | 12 | 2 13 | Objects In Python 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Chapter10/10_15_xml_states.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | def __init__(self, tag_name, parent=None): 3 | self.parent = parent 4 | self.tag_name = tag_name 5 | self.children = [] 6 | self.text="" 7 | 8 | def __str__(self): 9 | if self.text: 10 | return self.tag_name + ": " + self.text 11 | else: 12 | return self.tag_name 13 | 14 | class FirstTag: 15 | def process(self, remaining_string, parser): 16 | i_start_tag = remaining_string.find('<') 17 | i_end_tag = remaining_string.find('>') 18 | tag_name = remaining_string[i_start_tag+1:i_end_tag] 19 | root = Node(tag_name) 20 | parser.root = parser.current_node = root 21 | parser.state = ChildNode() 22 | return remaining_string[i_end_tag+1:] 23 | 24 | class ChildNode: 25 | def process(self, remaining_string, parser): 26 | stripped = remaining_string.strip() 27 | if stripped.startswith("') 39 | tag_name = remaining_string[i_start_tag+1:i_end_tag] 40 | node = Node(tag_name, parser.current_node) 41 | parser.current_node.children.append(node) 42 | parser.current_node = node 43 | parser.state = ChildNode() 44 | return remaining_string[i_end_tag+1:] 45 | 46 | class TextNode: 47 | def process(self, remaining_string, parser): 48 | i_start_tag = remaining_string.find('<') 49 | text = remaining_string[:i_start_tag] 50 | parser.current_node.text = text 51 | parser.state = ChildNode() 52 | return remaining_string[i_start_tag:] 53 | 54 | class CloseTag: 55 | def process(self, remaining_string, parser): 56 | i_start_tag = remaining_string.find('<') 57 | i_end_tag = remaining_string.find('>') 58 | assert remaining_string[i_start_tag+1] == "/" 59 | tag_name = remaining_string[i_start_tag+2:i_end_tag] 60 | assert tag_name == parser.current_node.tag_name 61 | parser.current_node = parser.current_node.parent 62 | parser.state = ChildNode() 63 | return remaining_string[i_end_tag+1:].strip() 64 | 65 | class Parser: 66 | def __init__(self, parse_string): 67 | self.parse_string = parse_string 68 | self.root = None 69 | self.current_node = None 70 | 71 | self.state = FirstTag() 72 | 73 | def process(self, remaining_string): 74 | remaining = self.state.process(remaining_string, self) 75 | if remaining: 76 | self.process(remaining) 77 | 78 | def start(self): 79 | self.process(self.parse_string) 80 | 81 | if __name__ == "__main__": 82 | import sys 83 | with open(sys.argv[1]) as file: 84 | contents = file.read() 85 | p = Parser(contents) 86 | p.start() 87 | 88 | nodes = [p.root] 89 | while nodes: 90 | node = nodes.pop(0) 91 | print(node) 92 | nodes = node.children + nodes 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /Chapter10/10_16_singleton_using_new.py: -------------------------------------------------------------------------------- 1 | class OneOnly: 2 | _singleton = None 3 | def __new__(cls, *args, **kwargs): 4 | if not cls._singleton: 5 | cls._singleton = super(OneOnly, cls).__new__( 6 | cls, *args, **kwargs) 7 | return cls._singleton 8 | -------------------------------------------------------------------------------- /Chapter10/10_17_xml_singletonstates.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | def __init__(self, tag_name, parent=None): 3 | self.parent = parent 4 | self.tag_name = tag_name 5 | self.children = [] 6 | self.text="" 7 | 8 | def __str__(self): 9 | if self.text: 10 | return self.tag_name + ": " + self.text 11 | else: 12 | return self.tag_name 13 | 14 | class FirstTag: 15 | def process(self, remaining_string, parser): 16 | i_start_tag = remaining_string.find('<') 17 | i_end_tag = remaining_string.find('>') 18 | tag_name = remaining_string[i_start_tag+1:i_end_tag] 19 | root = Node(tag_name) 20 | parser.root = parser.current_node = root 21 | parser.state = child_node 22 | return remaining_string[i_end_tag+1:] 23 | 24 | class ChildNode: 25 | def process(self, remaining_string, parser): 26 | stripped = remaining_string.strip() 27 | if stripped.startswith("') 39 | tag_name = remaining_string[i_start_tag+1:i_end_tag] 40 | node = Node(tag_name, parser.current_node) 41 | parser.current_node.children.append(node) 42 | parser.current_node = node 43 | parser.state = child_node 44 | return remaining_string[i_end_tag+1:] 45 | 46 | class TextNode: 47 | def process(self, remaining_string, parser): 48 | i_start_tag = remaining_string.find('<') 49 | text = remaining_string[:i_start_tag] 50 | parser.current_node.text = text 51 | parser.state = child_node 52 | return remaining_string[i_start_tag:] 53 | 54 | class CloseTag: 55 | def process(self, remaining_string, parser): 56 | i_start_tag = remaining_string.find('<') 57 | i_end_tag = remaining_string.find('>') 58 | assert remaining_string[i_start_tag+1] == "/" 59 | tag_name = remaining_string[i_start_tag+2:i_end_tag] 60 | assert tag_name == parser.current_node.tag_name 61 | parser.current_node = parser.current_node.parent 62 | parser.state = child_node 63 | return remaining_string[i_end_tag+1:].strip() 64 | 65 | first_tag = FirstTag() 66 | child_node = ChildNode() 67 | text_node = TextNode() 68 | open_tag = OpenTag() 69 | close_tag = CloseTag() 70 | 71 | class Parser: 72 | def __init__(self, parse_string): 73 | self.parse_string = parse_string 74 | self.root = None 75 | self.current_node = None 76 | 77 | self.state = first_tag 78 | 79 | def process(self, remaining_string): 80 | remaining = self.state.process(remaining_string, self) 81 | if remaining: 82 | self.process(remaining) 83 | 84 | def start(self): 85 | self.process(self.parse_string) 86 | 87 | if __name__ == "__main__": 88 | import sys 89 | with open(sys.argv[1]) as file: 90 | contents = file.read() 91 | p = Parser(contents) 92 | p.start() 93 | 94 | nodes = [p.root] 95 | while nodes: 96 | node = nodes.pop(0) 97 | print(node) 98 | nodes = node.children + nodes 99 | -------------------------------------------------------------------------------- /Chapter10/10_18_create_database_for_template.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | conn = sqlite3.connect("sales.db") 4 | 5 | conn.execute("CREATE TABLE Sales (salesperson text, " 6 | "amt currency, year integer, model text, new boolean)") 7 | conn.execute("INSERT INTO Sales values" 8 | " ('Tim', 16000, 2010, 'Honda Fit', 'true')") 9 | conn.execute("INSERT INTO Sales values" 10 | " ('Tim', 9000, 2006, 'Ford Focus', 'false')") 11 | conn.execute("INSERT INTO Sales values" 12 | " ('Gayle', 8000, 2004, 'Dodge Neon', 'false')") 13 | conn.execute("INSERT INTO Sales values" 14 | " ('Gayle', 28000, 2009, 'Ford Mustang', 'true')") 15 | conn.execute("INSERT INTO Sales values" 16 | " ('Gayle', 50000, 2010, 'Lincoln Navigator', 'true')") 17 | conn.execute("INSERT INTO Sales values" 18 | " ('Don', 20000, 2008, 'Toyota Prius', 'false')") 19 | conn.commit() 20 | conn.close() 21 | -------------------------------------------------------------------------------- /Chapter10/10_19_template_abstract_noimple.py: -------------------------------------------------------------------------------- 1 | class QueryTemplate: 2 | def connect(self): 3 | pass 4 | def construct_query(self): 5 | pass 6 | def do_query(self): 7 | pass 8 | def format_results(self): 9 | pass 10 | def output_results(self): 11 | pass 12 | 13 | def process_format(self): 14 | self.connect() 15 | self.construct_query() 16 | self.do_query() 17 | self.format_results() 18 | self.output_results() 19 | -------------------------------------------------------------------------------- /Chapter10/10_20_template_abstract_implemented.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | class QueryTemplate: 4 | def connect(self): 5 | self.conn = sqlite3.connect("sales.db") 6 | 7 | def construct_query(self): 8 | raise NotImplementedError() 9 | 10 | def do_query(self): 11 | results = self.conn.execute(self.query) 12 | self.results = results.fetchall() 13 | 14 | def format_results(self): 15 | output = [] 16 | for row in self.results: 17 | row =[str(i) for i in row] 18 | output.append(", ".join(row)) 19 | self.formatted_results = "\n".join(output) 20 | 21 | def output_results(self): 22 | raise NotImplementedError() 23 | 24 | def process_format(self): 25 | self.connect() 26 | self.construct_query() 27 | self.do_query() 28 | self.format_results() 29 | self.output_results() 30 | -------------------------------------------------------------------------------- /Chapter10/10_21_template_concretes.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | class QueryTemplate: 4 | def connect(self): 5 | self.conn = sqlite3.connect("sales.db") 6 | 7 | def construct_query(self): 8 | raise NotImplementedError() 9 | 10 | def do_query(self): 11 | results = self.conn.execute(self.query) 12 | self.results = results.fetchall() 13 | 14 | def format_results(self): 15 | output = [] 16 | for row in self.results: 17 | row =[str(i) for i in row] 18 | output.append(", ".join(row)) 19 | self.formatted_results = "\n".join(output) 20 | 21 | def output_results(self): 22 | raise NotImplementedError() 23 | 24 | def process_format(self): 25 | self.connect() 26 | self.construct_query() 27 | self.do_query() 28 | self.format_results() 29 | self.output_results() 30 | 31 | import datetime 32 | class NewVehiclesQuery(QueryTemplate): 33 | def construct_query(self): 34 | self.query = "select * from Sales where new='true'" 35 | 36 | def output_results(self): 37 | print(self.formatted_results) 38 | 39 | class UserGrossQuery(QueryTemplate): 40 | def construct_query(self): 41 | self.query = ("select salesperson, sum(amt) " + 42 | " from Sales group by salesperson") 43 | 44 | def output_results(self): 45 | filename = "gross_sales_{0}".format( 46 | datetime.date.today().strftime("%Y%m%d") 47 | ) 48 | with open(filename, 'w') as outfile: 49 | outfile.write(self.formatted_results) 50 | -------------------------------------------------------------------------------- /Chapter10/Benson.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter10/Benson.jpg -------------------------------------------------------------------------------- /Chapter10/TILED.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter10/TILED.jpg -------------------------------------------------------------------------------- /Chapter10/sales.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter10/sales.db -------------------------------------------------------------------------------- /Chapter11/11_01_age_calculator.py: -------------------------------------------------------------------------------- 1 | class AgeCalculator: 2 | def __init__(self, birthday): 3 | self.year, self.month, self.day = ( 4 | int(x) for x in birthday.split('-')) 5 | 6 | def calculate_age(self, date): 7 | year, month, day = ( 8 | int(x) for x in date.split('-')) 9 | age = year - self.year 10 | if (month,day) < (self.month,self.day): 11 | age -= 1 12 | return age 13 | -------------------------------------------------------------------------------- /Chapter11/11_02_age_calculator_adapted.py: -------------------------------------------------------------------------------- 1 | class AgeCalculator: 2 | def __init__(self, birthday): 3 | self.year, self.month, self.day = ( 4 | int(x) for x in birthday.split('-')) 5 | 6 | def calculate_age(self, date): 7 | year, month, day = ( 8 | int(x) for x in date.split('-')) 9 | age = year - self.year 10 | if (month,day) < (self.month,self.day): 11 | age -= 1 12 | return age 13 | 14 | class DateAgeAdapter: 15 | def _str_date(self, date): 16 | return date.strftime("%Y-%m-%d") 17 | 18 | def __init__(self, birthday): 19 | birthday = self._str_date(birthday) 20 | self.calculator = AgeCalculator(birthday) 21 | 22 | def get_age(self, date): 23 | date = self._str_date(date) 24 | return self.calculator.calculate_age(date) 25 | -------------------------------------------------------------------------------- /Chapter11/11_03_age_calculator_adapt_date.py: -------------------------------------------------------------------------------- 1 | class AgeCalculator: 2 | def __init__(self, birthday): 3 | self.year, self.month, self.day = ( 4 | int(x) for x in birthday.split('-')) 5 | 6 | def calculate_age(self, date): 7 | year, month, day = ( 8 | int(x) for x in date.split('-')) 9 | age = year - self.year 10 | if (month,day) < (self.month,self.day): 11 | age -= 1 12 | return age 13 | 14 | import datetime 15 | class AgeableDate(datetime.date): 16 | def split(self, char): 17 | return self.year, self.month, self.day 18 | -------------------------------------------------------------------------------- /Chapter11/11_04_email_facade.py: -------------------------------------------------------------------------------- 1 | import smtplib 2 | import imaplib 3 | 4 | class EmailFacade: 5 | def __init__(self, host, username, password): 6 | self.host = host 7 | self.username = username 8 | self.password = password 9 | 10 | def send_email(self, to_email, subject, message): 11 | if not "@" in self.username: 12 | from_email = "{0}@{1}".format( 13 | self.username, self.host) 14 | else: 15 | from_email = self.username 16 | message = ("From: {0}\r\n" 17 | "To: {1}\r\n" 18 | "Subject: {2}\r\n\r\n{3}").format( 19 | from_email, 20 | to_email, 21 | subject, 22 | message) 23 | 24 | smtp = smtplib.SMTP(self.host) 25 | smtp.login(self.username, self.password) 26 | smtp.sendmail(from_email, [to_email], message) 27 | 28 | def get_inbox(self): 29 | mailbox = imaplib.IMAP4(self.host) 30 | mailbox.login(bytes(self.username, 'utf8'), 31 | bytes(self.password, 'utf8')) 32 | mailbox.select() 33 | x, data = mailbox.search(None, 'ALL') 34 | messages = [] 35 | for num in data[0].split(): 36 | x, message = mailbox.fetch(num, '(RFC822)') 37 | messages.append(message[0][1]) 38 | return messages 39 | -------------------------------------------------------------------------------- /Chapter11/11_05_flyweight_factory.py: -------------------------------------------------------------------------------- 1 | import weakref 2 | 3 | class CarModel: 4 | _models = weakref.WeakValueDictionary() 5 | 6 | def __new__(cls, model_name, *args, **kwargs): 7 | model = cls._models.get(model_name) 8 | if not model: 9 | model = super().__new__(cls) 10 | cls._models[model_name] = model 11 | 12 | return model 13 | -------------------------------------------------------------------------------- /Chapter11/11_06_flyweight_init.py: -------------------------------------------------------------------------------- 1 | import weakref 2 | 3 | class CarModel: 4 | _models = weakref.WeakValueDictionary() 5 | 6 | def __new__(cls, model_name, *args, **kwargs): 7 | model = cls._models.get(model_name) 8 | if not model: 9 | model = super().__new__(cls) 10 | cls._models[model_name] = model 11 | 12 | return model 13 | 14 | def __init__(self, model_name, air=False, tilt=False, 15 | cruise_control=False, power_locks=False, 16 | alloy_wheels=False, usb_charger=False): 17 | if not hasattr(self, "initialized"): 18 | self.model_name = model_name 19 | self.air = air 20 | self.tilt = tilt 21 | self.cruise_control = cruise_control 22 | self.power_locks = power_locks 23 | self.alloy_wheels = alloy_wheels 24 | self.usb_charger = usb_charger 25 | self.initialized=True 26 | -------------------------------------------------------------------------------- /Chapter11/11_07_flyweight_check_serial.py: -------------------------------------------------------------------------------- 1 | import weakref 2 | 3 | class CarModel: 4 | _models = weakref.WeakValueDictionary() 5 | 6 | def __new__(cls, model_name, *args, **kwargs): 7 | model = cls._models.get(model_name) 8 | if not model: 9 | model = super().__new__(cls) 10 | cls._models[model_name] = model 11 | 12 | return model 13 | 14 | def __init__(self, model_name, air=False, tilt=False, 15 | cruise_control=False, power_locks=False, 16 | alloy_wheels=False, usb_charger=False): 17 | if not hasattr(self, "initted"): 18 | self.model_name = model_name 19 | self.air = air 20 | self.tilt = tilt 21 | self.cruise_control = cruise_control 22 | self.power_locks = power_locks 23 | self.alloy_wheels = alloy_wheels 24 | self.usb_charger = usb_charger 25 | self.initted=True 26 | 27 | def check_serial(self, serial_number): 28 | print("Sorry, we are unable to check " 29 | "the serial number {0} on the {1} " 30 | "at this time".format( 31 | serial_number, self.model_name)) 32 | 33 | -------------------------------------------------------------------------------- /Chapter11/11_08_car_class.py: -------------------------------------------------------------------------------- 1 | import weakref 2 | 3 | class CarModel: 4 | _models = weakref.WeakValueDictionary() 5 | 6 | def __new__(cls, model_name, *args, **kwargs): 7 | model = cls._models.get(model_name) 8 | if not model: 9 | model = super().__new__(cls) 10 | cls._models[model_name] = model 11 | 12 | return model 13 | 14 | def __init__(self, model_name, air=False, tilt=False, 15 | cruise_control=False, power_locks=False, 16 | alloy_wheels=False, usb_charger=False): 17 | if not hasattr(self, "initted"): 18 | self.model_name = model_name 19 | self.air = air 20 | self.tilt = tilt 21 | self.cruise_control = cruise_control 22 | self.power_locks = power_locks 23 | self.alloy_wheels = alloy_wheels 24 | self.usb_charger = usb_charger 25 | self.initted=True 26 | 27 | def check_serial(self, serial_number): 28 | print("Sorry, we are unable to check " 29 | "the serial number {0} on the {1} " 30 | "at this time".format( 31 | serial_number, self.model_name)) 32 | 33 | class Car: 34 | def __init__(self, model, color, serial): 35 | self.model = model 36 | self.color = color 37 | self.serial = serial 38 | 39 | def check_serial(self): 40 | return self.model.check_serial(self.serial) 41 | -------------------------------------------------------------------------------- /Chapter11/11_09_window_commands.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | class Window: 4 | def exit(self): 5 | sys.exit(0) 6 | 7 | class Document: 8 | def __init__(self, filename): 9 | self.filename = filename 10 | self.contents = "This file cannot be modified" 11 | 12 | def save(self): 13 | with open(self.filename, 'w') as file: 14 | file.write(self.contents) 15 | -------------------------------------------------------------------------------- /Chapter11/11_10_window_command_invokers.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | class Window: 4 | def exit(self): 5 | sys.exit(0) 6 | 7 | class Document: 8 | def __init__(self, filename): 9 | self.filename = filename 10 | self.contents = "This file cannot be modified" 11 | 12 | def save(self): 13 | with open(self.filename, 'w') as file: 14 | file.write(self.contents) 15 | 16 | class ToolbarButton: 17 | def __init__(self, name, iconname): 18 | self.name = name 19 | self.iconname = iconname 20 | 21 | def click(self): 22 | self.command.execute() 23 | 24 | class MenuItem: 25 | def __init__(self, menu_name, menuitem_name): 26 | self.menu = menu_name 27 | self.item = menuitem_name 28 | 29 | def click(self): 30 | self.command.execute() 31 | 32 | class KeyboardShortcut: 33 | def __init__(self, key, modifier): 34 | self.key = key 35 | self.modifier = modifier 36 | 37 | def keypress(self): 38 | self.command.execute() 39 | -------------------------------------------------------------------------------- /Chapter11/11_11_window_command_commands.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | class Window: 4 | def exit(self): 5 | sys.exit(0) 6 | 7 | class Document: 8 | def __init__(self, filename): 9 | self.filename = filename 10 | self.contents = "This file cannot be modified" 11 | 12 | def save(self): 13 | with open(self.filename, 'w') as file: 14 | file.write(self.contents) 15 | 16 | class ToolbarButton: 17 | def __init__(self, name, iconname): 18 | self.name = name 19 | self.iconname = iconname 20 | 21 | def click(self): 22 | self.command.execute() 23 | 24 | class MenuItem: 25 | def __init__(self, menu_name, menuitem_name): 26 | self.menu = menu_name 27 | self.item = menuitem_name 28 | 29 | def click(self): 30 | self.command.execute() 31 | 32 | class KeyboardShortcut: 33 | def __init__(self, key, modifier): 34 | self.key = key 35 | self.modifier = modifier 36 | 37 | def keypress(self): 38 | self.command.execute() 39 | 40 | class SaveCommand: 41 | def __init__(self, document): 42 | self.document = document 43 | 44 | def execute(self): 45 | self.document.save() 46 | 47 | class ExitCommand: 48 | def __init__(self, window): 49 | self.window = window 50 | 51 | def execute(self): 52 | self.window.exit() 53 | 54 | window = Window() 55 | document = Document("a_document.txt") 56 | save = SaveCommand(document) 57 | exit = ExitCommand(window) 58 | 59 | save_button = ToolbarButton('save', 'save.png') 60 | save_button.command = save 61 | save_keystroke = KeyboardShortcut("s", "ctrl") 62 | save_keystroke.command = save 63 | exit_menu = MenuItem("File", "Exit") 64 | exit_menu.command = exit 65 | 66 | -------------------------------------------------------------------------------- /Chapter11/11_12_window_command_function.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | class Window: 4 | def exit(self): 5 | sys.exit(0) 6 | 7 | class MenuItem: 8 | def click(self): 9 | self.command() 10 | 11 | window = Window() 12 | menu_item = MenuItem() 13 | menu_item.command = window.exit 14 | -------------------------------------------------------------------------------- /Chapter11/11_13_document_command_callable.py: -------------------------------------------------------------------------------- 1 | class Document: 2 | def __init__(self, filename): 3 | self.filename = filename 4 | self.contents = "This file cannot be modified" 5 | 6 | def save(self): 7 | with open(self.filename, 'w') as file: 8 | file.write(self.contents) 9 | 10 | class KeyboardShortcut: 11 | def keypress(self): 12 | self.command() 13 | 14 | class SaveCommand: 15 | def __init__(self, document): 16 | self.document = document 17 | 18 | def __call__(self): 19 | self.document.save() 20 | 21 | document = Document("a_file.txt") 22 | shortcut = KeyboardShortcut() 23 | save_command = SaveCommand(document) 24 | shortcut.command = save_command 25 | -------------------------------------------------------------------------------- /Chapter11/11_14_formatters.py: -------------------------------------------------------------------------------- 1 | class FranceDateFormatter: 2 | def format_date(self, y, m, d): 3 | y, m, d = (str(x) for x in (y,m,d)) 4 | y = '20' + y if len(y) == 2 else y 5 | m = '0' + m if len(m) == 1 else m 6 | d = '0' + d if len(d) == 1 else d 7 | return("{0}/{1}/{2}".format(d,m,y)) 8 | 9 | class USADateFormatter: 10 | def format_date(self, y, m, d): 11 | y, m, d = (str(x) for x in (y,m,d)) 12 | y = '20' + y if len(y) == 2 else y 13 | m = '0' + m if len(m) == 1 else m 14 | d = '0' + d if len(d) == 1 else d 15 | return("{0}-{1}-{2}".format(m,d,y)) 16 | 17 | class FranceCurrencyFormatter: 18 | def format_currency(self, base, cents): 19 | base, cents = (str(x) for x in (base, cents)) 20 | if len(cents) == 0: 21 | cents = '00' 22 | elif len(cents) == 1: 23 | cents = '0' + cents 24 | 25 | digits = [] 26 | for i,c in enumerate(reversed(base)): 27 | if i and not i % 3: 28 | digits.append(' ') 29 | digits.append(c) 30 | base = ''.join(reversed(digits)) 31 | return "{0}€{1}".format(base, cents) 32 | 33 | class USACurrencyFormatter: 34 | def format_currency(self, base, cents): 35 | base, cents = (str(x) for x in (base, cents)) 36 | if len(cents) == 0: 37 | cents = '00' 38 | elif len(cents) == 1: 39 | cents = '0' + cents 40 | 41 | digits = [] 42 | for i,c in enumerate(reversed(base)): 43 | if i and not i % 3: 44 | digits.append(',') 45 | digits.append(c) 46 | base = ''.join(reversed(digits)) 47 | return "${0}.{1}".format(base, cents) 48 | 49 | -------------------------------------------------------------------------------- /Chapter11/11_15_formatter_factories.py: -------------------------------------------------------------------------------- 1 | class FranceDateFormatter: 2 | def format_date(self, y, m, d): 3 | y, m, d = (str(x) for x in (y,m,d)) 4 | y = '20' + y if len(y) == 2 else y 5 | m = '0' + m if len(m) == 1 else m 6 | d = '0' + d if len(d) == 1 else d 7 | return("{0}/{1}/{2}".format(d,m,y)) 8 | 9 | class USADateFormatter: 10 | def format_date(self, y, m, d): 11 | y, m, d = (str(x) for x in (y,m,d)) 12 | y = '20' + y if len(y) == 2 else y 13 | m = '0' + m if len(m) == 1 else m 14 | d = '0' + d if len(d) == 1 else d 15 | return("{0}-{1}-{2}".format(m,d,y)) 16 | 17 | class FranceCurrencyFormatter: 18 | def format_currency(self, base, cents): 19 | base, cents = (str(x) for x in (base, cents)) 20 | if len(cents) == 0: 21 | cents = '00' 22 | elif len(cents) == 1: 23 | cents = '0' + cents 24 | 25 | digits = [] 26 | for i,c in enumerate(reversed(base)): 27 | if i and not i % 3: 28 | digits.append(' ') 29 | digits.append(c) 30 | base = ''.join(reversed(digits)) 31 | return "{0}€{1}".format(base, cents) 32 | 33 | class USACurrencyFormatter: 34 | def format_currency(self, base, cents): 35 | base, cents = (str(x) for x in (base, cents)) 36 | if len(cents) == 0: 37 | cents = '00' 38 | elif len(cents) == 1: 39 | cents = '0' + cents 40 | 41 | digits = [] 42 | for i,c in enumerate(reversed(base)): 43 | if i and not i % 3: 44 | digits.append(',') 45 | digits.append(c) 46 | base = ''.join(reversed(digits)) 47 | return "${0}.{1}".format(base, cents) 48 | 49 | class USAFormatterFactory: 50 | def create_date_formatter(self): 51 | return USADateFormatter() 52 | def create_currency_formatter(self): 53 | return USACurrencyFormatter() 54 | 55 | class FranceFormatterFactory: 56 | def create_date_formatter(self): 57 | return FranceDateFormatter() 58 | def create_currency_formatter(self): 59 | return FranceCurrencyFormatter() 60 | 61 | country_code = "US" 62 | factory_map = { 63 | "US": USAFormatterFactory, 64 | "FR": FranceFormatterFactory} 65 | formatter_factory = factory_map.get(country_code)() 66 | -------------------------------------------------------------------------------- /Chapter11/11_16_composite_folder_methods.py: -------------------------------------------------------------------------------- 1 | class Folder: 2 | def __init__(self, name): 3 | self.name = name 4 | self.children = {} 5 | 6 | def add_child(self, child): 7 | pass 8 | 9 | def move(self, new_path): 10 | pass 11 | 12 | def copy(self, new_path): 13 | pass 14 | 15 | def delete(self): 16 | pass 17 | 18 | class File: 19 | def __init__(self, name, contents): 20 | self.name = name 21 | self.contents = contents 22 | 23 | def move(self, new_path): 24 | pass 25 | 26 | def copy(self, new_path): 27 | pass 28 | 29 | def delete(self): 30 | pass 31 | -------------------------------------------------------------------------------- /Chapter11/11_17_component_hierarchy.py: -------------------------------------------------------------------------------- 1 | class Component: 2 | def __init__(self, name): 3 | self.name = name 4 | 5 | def move(self, new_path): 6 | new_folder =get_path(new_path) 7 | del self.parent.children[self.name] 8 | new_folder.children[self.name] = self 9 | self.parent = new_folder 10 | 11 | def delete(self): 12 | del self.parent.children[self.name] 13 | 14 | class Folder(Component): 15 | def __init__(self, name): 16 | super().__init__(name) 17 | self.children = {} 18 | 19 | def add_child(self, child): 20 | pass 21 | 22 | def copy(self, new_path): 23 | pass 24 | 25 | class File(Component): 26 | def __init__(self, name, contents): 27 | super().__init__(name) 28 | self.contents = contents 29 | 30 | def copy(self, new_path): 31 | pass 32 | 33 | root = Folder('') 34 | def get_path(path): 35 | names = path.split('/')[1:] 36 | node = root 37 | for name in names: 38 | node = node.children[name] 39 | return node 40 | 41 | -------------------------------------------------------------------------------- /Chapter11/11_18_add_child.py: -------------------------------------------------------------------------------- 1 | class Component: 2 | def __init__(self, name): 3 | self.name = name 4 | 5 | def move(self, new_path): 6 | new_folder =get_path(new_path) 7 | del self.parent.children[self.name] 8 | new_folder.children[self.name] = self 9 | self.parent = new_folder 10 | 11 | def delete(self): 12 | del self.parent.children[self.name] 13 | 14 | class Folder(Component): 15 | def __init__(self, name): 16 | super().__init__(name) 17 | self.children = {} 18 | 19 | def add_child(self, child): 20 | child.parent = self 21 | self.children[child.name] = child 22 | 23 | def copy(self, new_path): 24 | pass 25 | 26 | class File(Component): 27 | def __init__(self, name, contents): 28 | super().__init__(name) 29 | self.contents = contents 30 | 31 | def copy(self, new_path): 32 | pass 33 | 34 | root = Folder('') 35 | def get_path(path): 36 | names = path.split('/')[1:] 37 | node = root 38 | for name in names: 39 | node = node.children[name] 40 | return node 41 | 42 | -------------------------------------------------------------------------------- /Chapter12/12_01_simplest_unittest.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | class CheckNumbers(unittest.TestCase): 4 | def test_int_float(self): 5 | self.assertEqual(1, 1.0) 6 | 7 | def test_str_float(self): 8 | self.assertEqual(1, "1") 9 | 10 | if __name__ == "__main__": 11 | unittest.main() 12 | -------------------------------------------------------------------------------- /Chapter12/12_02_assertraises_python.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | def average(seq): 4 | return sum(seq) / len(seq) 5 | 6 | class TestAverage(unittest.TestCase): 7 | def test_zero(self): 8 | self.assertRaises(ZeroDivisionError, 9 | average, 10 | []) 11 | 12 | def test_with_zero(self): 13 | with self.assertRaises(ZeroDivisionError): 14 | average([]) 15 | 16 | if __name__ == "__main__": 17 | unittest.main() 18 | -------------------------------------------------------------------------------- /Chapter12/12_03_stats.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | class StatsList(list): 4 | def mean(self): 5 | return sum(self) / len(self) 6 | 7 | def median(self): 8 | if len(self) % 2: 9 | return self[int(len(self) / 2)] 10 | else: 11 | idx = int(len(self) / 2) 12 | return (self[idx] + self[idx-1]) / 2 13 | 14 | def mode(self): 15 | freqs = defaultdict(int) 16 | for item in self: 17 | freqs[item] += 1 18 | mode_freq = max(freqs.values()) 19 | modes = [] 20 | for item, value in freqs.items(): 21 | if value == mode_freq: 22 | modes.append(item) 23 | return modes 24 | -------------------------------------------------------------------------------- /Chapter12/12_04_test_stats.py: -------------------------------------------------------------------------------- 1 | from stats import StatsList 2 | import unittest 3 | 4 | class TestValidInputs(unittest.TestCase): 5 | def setUp(self): 6 | self.stats = StatsList([1,2,2,3,3,4]) 7 | 8 | def test_mean(self): 9 | self.assertEqual(self.stats.mean(), 2.5) 10 | 11 | def test_median(self): 12 | self.assertEqual(self.stats.median(), 2.5) 13 | self.stats.append(4) 14 | self.assertEqual(self.stats.median(), 3) 15 | 16 | def test_mode(self): 17 | self.assertEqual(self.stats.mode(), [2,3]) 18 | self.stats.remove(2) 19 | self.assertEqual(self.stats.mode(), [3]) 20 | 21 | if __name__ == "__main__": 22 | unittest.main() 23 | -------------------------------------------------------------------------------- /Chapter12/12_05_skipping_tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sys 3 | 4 | class SkipTests(unittest.TestCase): 5 | @unittest.expectedFailure 6 | def test_fails(self): 7 | self.assertEqual(False, True) 8 | 9 | @unittest.skip("Test is useless") 10 | def test_skip(self): 11 | self.assertEqual(False, True) 12 | 13 | @unittest.skipIf(sys.version_info.minor == 4, 14 | "broken on 3.4") 15 | def test_skipif(self): 16 | self.assertEqual(False, True) 17 | 18 | @unittest.skipUnless(sys.platform.startswith('linux'), 19 | "broken unless on linux") 20 | def test_skipunless(self): 21 | self.assertEqual(False, True) 22 | 23 | if __name__ == "__main__": 24 | unittest.main() 25 | -------------------------------------------------------------------------------- /Chapter12/12_06_simplestpytest.py: -------------------------------------------------------------------------------- 1 | def test_int_float(): 2 | assert 1 == 1.0 3 | -------------------------------------------------------------------------------- /Chapter12/12_06_simplestpytest.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter12/12_06_simplestpytest.pyc -------------------------------------------------------------------------------- /Chapter12/12_07_class_pytest.py: -------------------------------------------------------------------------------- 1 | 2 | class TestNumbers: 3 | def test_int_float(self): 4 | assert 1 == 1.0 5 | 6 | def test_int_str(self): 7 | assert 1 == "1" 8 | -------------------------------------------------------------------------------- /Chapter12/12_07_class_pytest.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter12/12_07_class_pytest.pyc -------------------------------------------------------------------------------- /Chapter12/12_08_setup_teardown.py: -------------------------------------------------------------------------------- 1 | def setup_module(module): 2 | print("setting up MODULE {0}".format( 3 | module.__name__)) 4 | 5 | def teardown_module(module): 6 | print("tearing down MODULE {0}".format( 7 | module.__name__)) 8 | 9 | def test_a_function(): 10 | print("RUNNING TEST FUNCTION") 11 | 12 | class BaseTest: 13 | def setup_class(cls): 14 | print("setting up CLASS {0}".format( 15 | cls.__name__)) 16 | 17 | def teardown_class(cls): 18 | print("tearing down CLASS {0}\n".format( 19 | cls.__name__)) 20 | 21 | def setup_method(self, method): 22 | print("setting up METHOD {0}".format( 23 | method.__name__)) 24 | 25 | def teardown_method(self, method): 26 | print("tearing down METHOD {0}".format( 27 | method.__name__)) 28 | 29 | class TestClass1(BaseTest): 30 | def test_method_1(self): 31 | print("RUNNING METHOD 1-1") 32 | 33 | def test_method_2(self): 34 | print("RUNNING METHOD 1-2") 35 | 36 | class TestClass2(BaseTest): 37 | def test_method_1(self): 38 | print("RUNNING METHOD 2-1") 39 | 40 | def test_method_2(self): 41 | print("RUNNING METHOD 2-2") 42 | -------------------------------------------------------------------------------- /Chapter12/12_08_setup_teardown.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter12/12_08_setup_teardown.pyc -------------------------------------------------------------------------------- /Chapter12/12_09_funcargs.py: -------------------------------------------------------------------------------- 1 | from stats import StatsList 2 | 3 | def pytest_funcarg__valid_stats(request): 4 | return StatsList([1,2,2,3,3,4]) 5 | 6 | 7 | def test_mean(valid_stats): 8 | assert valid_stats.mean() == 2.5 9 | 10 | def test_median(valid_stats): 11 | assert valid_stats.median() == 2.5 12 | valid_stats.append(4) 13 | assert valid_stats.median() == 3 14 | 15 | def test_mode(valid_stats): 16 | assert valid_stats.mode() == [2,3] 17 | valid_stats.remove(2) 18 | assert valid_stats.mode() == [3] 19 | -------------------------------------------------------------------------------- /Chapter12/12_09_funcargs.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter12/12_09_funcargs.pyc -------------------------------------------------------------------------------- /Chapter12/12_10_funcarg_finalizer.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | import shutil 3 | import os.path 4 | 5 | def pytest_funcarg__temp_dir(request): 6 | dir = tempfile.mkdtemp() 7 | print(dir) 8 | 9 | def cleanup(): 10 | shutil.rmtree(dir) 11 | request.addfinalizer(cleanup) 12 | return dir 13 | 14 | def test_osfiles(temp_dir): 15 | os.mkdir(os.path.join(temp_dir, 'a')) 16 | os.mkdir(os.path.join(temp_dir, 'b')) 17 | dir_contents = os.listdir(temp_dir) 18 | assert len(dir_contents) == 2 19 | assert 'a' in dir_contents 20 | assert 'b' in dir_contents 21 | -------------------------------------------------------------------------------- /Chapter12/12_10_funcarg_finalizer.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter12/12_10_funcarg_finalizer.pyc -------------------------------------------------------------------------------- /Chapter12/12_11_echo_server.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 4 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 5 | s.bind(('localhost',1028)) 6 | s.listen(1) 7 | 8 | try: 9 | while True: 10 | client, address = s.accept() 11 | data = client.recv(1024) 12 | client.send(data) 13 | client.close() 14 | finally: 15 | s.close() 16 | -------------------------------------------------------------------------------- /Chapter12/12_12_pytest_echo.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import socket 3 | import time 4 | 5 | def pytest_funcarg__echoserver(request): 6 | def setup(): 7 | p = subprocess.Popen( 8 | ['python3', '12_11_echo_server.py']) 9 | time.sleep(1) 10 | return p 11 | 12 | def cleanup(p): 13 | p.terminate() 14 | 15 | return request.cached_setup( 16 | setup=setup, 17 | teardown=cleanup, 18 | scope="session") 19 | 20 | def pytest_funcarg__clientsocket(request): 21 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 22 | s.connect(('localhost', 1028)) 23 | request.addfinalizer(lambda: s.close()) 24 | return s 25 | 26 | def test_echo(echoserver, clientsocket): 27 | clientsocket.send(b"abc") 28 | assert clientsocket.recv(3) == b'abc' 29 | 30 | def test_echo2(echoserver, clientsocket): 31 | clientsocket.send(b"def") 32 | assert clientsocket.recv(3) == b'def' 33 | -------------------------------------------------------------------------------- /Chapter12/12_12_pytest_echo.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter12/12_12_pytest_echo.pyc -------------------------------------------------------------------------------- /Chapter12/12_13_pytest_simple_skip.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import py.test 3 | 4 | def test_simple_skip(): 5 | if sys.platform != "fakeos": 6 | py.test.skip("Test works only on fakeOS") 7 | 8 | fakeos.do_something_fake() 9 | assert fakeos.did_not_happen 10 | -------------------------------------------------------------------------------- /Chapter12/12_13_pytest_simple_skip.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter12/12_13_pytest_simple_skip.pyc -------------------------------------------------------------------------------- /Chapter12/12_14_pytest_importorskip.py: -------------------------------------------------------------------------------- 1 | import py.test 2 | 3 | py.test.importorskip("postgresql") 4 | 5 | def test_connect(): 6 | pass 7 | 8 | def test_query(): 9 | pass 10 | -------------------------------------------------------------------------------- /Chapter12/12_14_pytest_importorskip.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter12/12_14_pytest_importorskip.pyc -------------------------------------------------------------------------------- /Chapter12/12_15_pytest_skipifmark.py: -------------------------------------------------------------------------------- 1 | import py.test 2 | 3 | @py.test.mark.skipif("sys.version_info <= (3,0)") 4 | def test_python3(): 5 | assert b"hello".decode() == "hello" 6 | -------------------------------------------------------------------------------- /Chapter12/12_15_pytest_skipifmark.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter12/12_15_pytest_skipifmark.pyc -------------------------------------------------------------------------------- /Chapter12/12_16_mock_flightstatus.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import redis 3 | 4 | class FlightStatusTracker: 5 | ALLOWED_STATUSES = {'CANCELLED', 'DELAYED', 'ON TIME'} 6 | 7 | def __init__(self): 8 | self.redis = redis.StrictRedis() 9 | 10 | def change_status(self, flight, status): 11 | status = status.upper() 12 | if status not in self.ALLOWED_STATUSES: 13 | raise ValueError( 14 | "{} is not a valid status".format(status) 15 | ) 16 | 17 | key = "flightno:{}".format(flight) 18 | value = "{}|{}".format(datetime.datetime.now().isoformat(), status) 19 | self.redis.set(key, value) 20 | 21 | def last_status(self, flight, status): 22 | key = "flightno:{}".format(flight) 23 | timestamp, status = self.redis.get(key).split('|') 24 | return timestamp, status 25 | 26 | 27 | 28 | from unittest.mock import Mock 29 | import py.test 30 | def pytest_funcarg__tracker(): 31 | return FlightStatusTracker() 32 | 33 | def test_mock_method(tracker): 34 | tracker.redis.set = Mock() 35 | with py.test.raises(ValueError) as ex: 36 | tracker.change_status("AC101", "lost") 37 | assert ex.value.args[0] == "LOST is not a valid status" 38 | assert tracker.redis.set.call_count == 0 39 | 40 | from unittest.mock import patch 41 | def test_patch(tracker): 42 | tracker.redis.set = Mock() 43 | fake_now = datetime.datetime(2015, 4, 1) 44 | with patch('datetime.datetime') as dt: 45 | dt.now.return_value = fake_now 46 | tracker.change_status("AC102", "on time") 47 | dt.now.assert_called_once_with() 48 | tracker.redis.set.assert_called_once_with( 49 | "flightno:AC102", 50 | "2015-04-01T00:00:00|ON TIME" 51 | ) 52 | 53 | -------------------------------------------------------------------------------- /Chapter12/12_17_mock_redis.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import redis 3 | 4 | class FlightStatusTracker: 5 | ALLOWED_STATUSES = {'CANCELLED', 'DELAYED', 'ON TIME', 'ARRIVED'} 6 | 7 | 8 | def __init__(self, redis_instance=None): 9 | self.redis = redis_instance if redis_instance else redis.StrictRedis() 10 | 11 | 12 | 13 | def change_status(self, flight, status): 14 | status = status.upper() 15 | if status not in self.ALLOWED_STATUSES: 16 | raise ValueError( 17 | "{} is not a valid status".format(status) 18 | ) 19 | 20 | key = "flightno:{}".format(flight) 21 | value = "{}|{}".format(datetime.datetime.now().isoformat(), status) 22 | self.redis.set(key, value) 23 | -------------------------------------------------------------------------------- /Chapter12/12_30_coverage_unittest.py: -------------------------------------------------------------------------------- 1 | from stats import StatsList 2 | import unittest 3 | 4 | class TestMean(unittest.TestCase): 5 | def test_mean(self): 6 | self.assertEqual(StatsList([1,2,2,3,3,4]).mean(), 2.5) 7 | 8 | if __name__ == "__main__": 9 | unittest.main() 10 | -------------------------------------------------------------------------------- /Chapter12/__pycache__/stats.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter12/__pycache__/stats.cpython-34.pyc -------------------------------------------------------------------------------- /Chapter12/__pycache__/test_mock.cpython-34-PYTEST.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter12/__pycache__/test_mock.cpython-34-PYTEST.pyc -------------------------------------------------------------------------------- /Chapter12/casestudy/__pycache__/test_vigenere_cipher.cpython-34-PYTEST.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter12/casestudy/__pycache__/test_vigenere_cipher.cpython-34-PYTEST.pyc -------------------------------------------------------------------------------- /Chapter12/casestudy/__pycache__/vigenere_cipher.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter12/casestudy/__pycache__/vigenere_cipher.cpython-34.pyc -------------------------------------------------------------------------------- /Chapter12/casestudy/test_vigenere_cipher.py: -------------------------------------------------------------------------------- 1 | from vigenere_cipher import VigenereCipher 2 | from vigenere_cipher import combine_character 3 | from vigenere_cipher import separate_character 4 | 5 | def test_encode(): 6 | cipher = VigenereCipher("TRAIN") 7 | encoded = cipher.encode("ENCODEDINPYTHON") 8 | assert encoded == "XECWQXUIVCRKHWA" 9 | 10 | def test_encode_character(): 11 | cipher = VigenereCipher("TRAIN") 12 | encoded = cipher.encode("E") 13 | assert encoded == "X" 14 | 15 | def test_encode_spaces(): 16 | cipher = VigenereCipher("TRAIN") 17 | encoded = cipher.encode("ENCODED IN PYTHON") 18 | assert encoded == "XECWQXUIVCRKHWA" 19 | 20 | def test_encode_lowercase(): 21 | cipher = VigenereCipher("TRain") 22 | encoded = cipher.encode("encoded in Python") 23 | assert encoded == "XECWQXUIVCRKHWA" 24 | 25 | def test_combine_character(): 26 | assert combine_character("E", "T") == "X" 27 | assert combine_character("N", "R") == "E" 28 | 29 | def test_extend_keyword(): 30 | cipher = VigenereCipher("TRAIN") 31 | extended = cipher.extend_keyword(16) 32 | assert extended == "TRAINTRAINTRAINT" 33 | 34 | def test_extend_keyword_even(): 35 | cipher = VigenereCipher("TRAIN") 36 | extended = cipher.extend_keyword(15) 37 | assert extended == "TRAINTRAINTRAIN" 38 | 39 | def test_separate_character(): 40 | assert separate_character("X", "T") == "E" 41 | assert separate_character("E", "R") == "N" 42 | 43 | def test_decode(): 44 | cipher = VigenereCipher("TRAIN") 45 | decoded = cipher.decode("XECWQXUIVCRKHWA") 46 | assert decoded == "ENCODEDINPYTHON" 47 | -------------------------------------------------------------------------------- /Chapter12/casestudy/test_vigenere_cipher.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter12/casestudy/test_vigenere_cipher.pyc -------------------------------------------------------------------------------- /Chapter12/casestudy/vigenere_cipher.py: -------------------------------------------------------------------------------- 1 | def combine_character(plain, keyword): 2 | plain = plain.upper() 3 | keyword = keyword.upper() 4 | plain_num = ord(plain) - ord('A') 5 | keyword_num = ord(keyword) - ord('A') 6 | return chr(ord('A') + (plain_num + keyword_num) % 26) 7 | 8 | def separate_character(cypher, keyword): 9 | cypher = cypher.upper() 10 | keyword = keyword.upper() 11 | cypher_num = ord(cypher) - ord('A') 12 | keyword_num = ord(keyword) - ord('A') 13 | return chr(ord('A') + (cypher_num - keyword_num) % 26) 14 | 15 | class VigenereCipher: 16 | def __init__(self, keyword): 17 | self.keyword = keyword 18 | 19 | def extend_keyword(self, number): 20 | repeats = number // len(self.keyword) + 1 21 | return (self.keyword * repeats)[:number] 22 | 23 | def _code(self, text, combine_func): 24 | text = text.replace(" ", "").upper() 25 | combined = [] 26 | keyword = self.extend_keyword(len(text)) 27 | for p,k in zip(text, keyword): 28 | combined.append(combine_func(p,k)) 29 | return "".join(combined) 30 | 31 | def encode(self, plaintext): 32 | return self._code(plaintext, combine_character) 33 | 34 | def decode(self, ciphertext): 35 | return self._code(ciphertext, separate_character) 36 | -------------------------------------------------------------------------------- /Chapter12/casestudy/vigenere_cipher.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter12/casestudy/vigenere_cipher.pyc -------------------------------------------------------------------------------- /Chapter12/casestudy/vigenere_cipher1.py: -------------------------------------------------------------------------------- 1 | def combine_character(plain, keyword): 2 | plain = plain.upper() 3 | keyword = keyword.upper() 4 | plain_num = ord(plain) - ord('A') 5 | keyword_num = ord(keyword) - ord('A') 6 | return chr(ord('A') + (plain_num + keyword_num) % 26) 7 | 8 | def separate_character(cypher, keyword): 9 | cypher = cypher.upper() 10 | keyword = keyword.upper() 11 | cypher_num = ord(cypher) - ord('A') 12 | keyword_num = ord(keyword) - ord('A') 13 | return chr(ord('A') + (cypher_num - keyword_num) % 26) 14 | 15 | class VigenereCipher: 16 | def __init__(self, keyword): 17 | self.keyword = keyword 18 | 19 | def extend_keyword(self, number): 20 | repeats = number // len(self.keyword) + 1 21 | return (self.keyword * repeats)[:number] 22 | 23 | def encode(self, plaintext): 24 | plaintext = plaintext.replace(" ", "").upper() 25 | cipher = [] 26 | keyword = self.extend_keyword(len(plaintext)) 27 | for p,k in zip(plaintext, keyword): 28 | cipher.append(combine_character(p,k)) 29 | return "".join(cipher) 30 | 31 | def decode(self, ciphertext): 32 | plain = [] 33 | keyword = self.extend_keyword(len(ciphertext)) 34 | for p,k in zip(ciphertext, keyword): 35 | plain.append(separate_character(p,k)) 36 | return "".join(plain) 37 | -------------------------------------------------------------------------------- /Chapter12/dump.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter12/dump.rdb -------------------------------------------------------------------------------- /Chapter12/stats.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | class StatsList(list): 4 | def mean(self): 5 | return sum(self) / len(self) 6 | 7 | def median(self): 8 | if len(self) % 2: 9 | return self[int(len(self) / 2)] 10 | else: 11 | idx = int(len(self) / 2) 12 | return (self[idx] + self[idx-1]) / 2 13 | 14 | def mode(self): 15 | freqs = defaultdict(int) 16 | for item in self: 17 | freqs[item] += 1 18 | mode_freq = max(freqs.values()) 19 | modes = [] 20 | for item, value in freqs.items(): 21 | if value == mode_freq: 22 | modes.append(item) 23 | return modes 24 | -------------------------------------------------------------------------------- /Chapter12/stats.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter12/stats.pyc -------------------------------------------------------------------------------- /Chapter12/test_mock.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import redis 3 | 4 | class FlightStatusTracker: 5 | ALLOWED_STATUSES = {'CANCELLED', 'DELAYED', 'ON TIME', 'ARRIVED'} 6 | 7 | def __init__(self, redis_instance=None): 8 | self.redis = if redis_instance else redis.StrictRedis() 9 | 10 | def change_status(self, flight, status): 11 | status = status.upper() 12 | if status not in self.ALLOWED_STATUSES: 13 | raise ValueError( 14 | "{} is not a valid status".format(status) 15 | ) 16 | 17 | key = "flightno:{}".format(flight) 18 | value = "{}|{}".format(datetime.datetime.now().isoformat(), status) 19 | self.redis.set(key, value) 20 | -------------------------------------------------------------------------------- /Chapter13/13_01_two_basic_threads.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | 3 | class InputReader(Thread): 4 | def run(self): 5 | self.line_of_text = input() 6 | 7 | print("Enter some text and press enter: ") 8 | thread = InputReader() 9 | thread.start() 10 | 11 | count = result = 1 12 | while thread.is_alive(): 13 | result = count * count 14 | count += 1 15 | 16 | print("calculated squares up to {0} * {0} = {1}".format( 17 | count, result)) 18 | print("while you typed '{}'".format(thread.line_of_text)) 19 | -------------------------------------------------------------------------------- /Chapter13/13_02_thread_wait.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | import json 3 | from urllib.request import urlopen 4 | import time 5 | 6 | CITIES = [ 7 | 'Edmonton', 'Victoria', 'Winnipeg', 'Fredericton', 8 | "St. John's", 'Halifax', 'Toronto', 'Charlottetown', 9 | 'Quebec City', 'Regina' 10 | ] 11 | 12 | class TempGetter(Thread): 13 | def __init__(self, city): 14 | super().__init__() 15 | self.city = city 16 | 17 | def run(self): 18 | url_template = ( 19 | 'http://api.openweathermap.org/data/2.5/' 20 | 'weather?q={},CA&units=metric') 21 | response = urlopen(url_template.format(self.city)) 22 | data = json.loads(response.read().decode()) 23 | self.temperature = data['main']['temp'] 24 | 25 | threads = [TempGetter(c) for c in CITIES] 26 | start = time.time() 27 | for thread in threads: 28 | thread.start() 29 | 30 | for thread in threads: 31 | thread.join() 32 | 33 | for thread in threads: 34 | print( 35 | "it is {0.temperature:.0f}°C in {0.city}".format(thread)) 36 | print( 37 | "Got {} temps in {} seconds".format( 38 | len(threads), time.time() - start)) 39 | -------------------------------------------------------------------------------- /Chapter13/13_03_parallel.py: -------------------------------------------------------------------------------- 1 | from multiprocessing import Process, cpu_count 2 | from threading import Thread 3 | import time 4 | import os 5 | 6 | class MuchCPU(Process): 7 | def run(self): 8 | print(os.getpid()) 9 | for i in range(200000000): 10 | pass 11 | 12 | if __name__ == '__main__': 13 | procs = [MuchCPU() for f in range(cpu_count())] 14 | 15 | t = time.time() 16 | for p in procs: 17 | p.start() 18 | for p in procs: 19 | p.join() 20 | print('work took {} seconds'.format(time.time() - t)) 21 | -------------------------------------------------------------------------------- /Chapter13/13_04_prime_factor.py: -------------------------------------------------------------------------------- 1 | import random 2 | from multiprocessing.pool import Pool 3 | 4 | def prime_factor(value): 5 | factors = [] 6 | for divisor in range(2, value-1): 7 | quotient, remainder = divmod(value, divisor) 8 | if not remainder: 9 | factors.extend(prime_factor(divisor)) 10 | factors.extend(prime_factor(quotient)) 11 | break 12 | else: 13 | factors = [value] 14 | return factors 15 | 16 | if __name__ == '__main__': 17 | pool = Pool() 18 | 19 | to_factor = [ 20 | random.randint(100000, 50000000) for i in range(20) 21 | ] 22 | results = pool.map(prime_factor, to_factor) 23 | for value, factors in zip(to_factor, results): 24 | print("The factors of {} are {}".format(value, factors)) 25 | -------------------------------------------------------------------------------- /Chapter13/13_05_post_search.py: -------------------------------------------------------------------------------- 1 | def search(paths, query_q, results_q): 2 | lines = [] 3 | for path in paths: 4 | lines.extend(l.strip() for l in path.open()) 5 | 6 | query = query_q.get() 7 | while query: 8 | results_q.put([l for l in lines if query in l]) 9 | query = query_q.get() 10 | 11 | if __name__ == '__main__': 12 | from multiprocessing import Process, Queue, cpu_count 13 | from path import path 14 | cpus = cpu_count() 15 | pathnames = [f for f in path('.').listdir() if f.isfile()] 16 | paths = [pathnames[i::cpus] for i in range(cpus)] 17 | query_queues = [Queue() for p in range(cpus)] 18 | results_queue = Queue() 19 | 20 | search_procs = [ 21 | Process(target=search, args=(p, q, results_queue)) 22 | for p, q in zip(paths, query_queues) 23 | ] 24 | for proc in search_procs: proc.start() 25 | 26 | for q in query_queues: 27 | q.put("def") 28 | q.put(None) # Signal process termination 29 | 30 | for i in range(cpus): 31 | for match in results_queue.get(): 32 | print(match) 33 | for proc in search_procs: proc.join() 34 | -------------------------------------------------------------------------------- /Chapter13/13_06_futures.py: -------------------------------------------------------------------------------- 1 | from concurrent.futures import ThreadPoolExecutor 2 | from pathlib import Path 3 | from os.path import sep as pathsep 4 | from collections import deque 5 | 6 | def find_files(path, query_string): 7 | subdirs = [] 8 | for p in path.iterdir(): 9 | full_path = str(p.absolute()) 10 | if p.is_dir() and not p.is_symlink(): 11 | subdirs.append(p) 12 | if query_string in full_path: 13 | print(full_path) 14 | 15 | return subdirs 16 | 17 | query = '.py' 18 | futures = deque() 19 | basedir = Path(pathsep).absolute() 20 | 21 | with ThreadPoolExecutor(max_workers=10) as executor: 22 | futures.append( 23 | executor.submit(find_files, basedir, query)) 24 | while futures: 25 | future = futures.popleft() 26 | if future.exception(): 27 | continue 28 | elif future.done(): 29 | subdirs = future.result() 30 | for subdir in subdirs: 31 | futures.append( 32 | executor.submit(find_files, subdir, query)) 33 | else: 34 | futures.append(future) 35 | -------------------------------------------------------------------------------- /Chapter13/13_07_basic_async.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | 4 | @asyncio.coroutine 5 | def random_sleep(counter): 6 | delay = random.random() * 5 7 | print("{} sleeps for {:.2f} seconds".format(counter, delay)) 8 | yield from asyncio.sleep(delay) 9 | print("{} awakens".format(counter)) 10 | 11 | @asyncio.coroutine 12 | def five_sleepers(): 13 | print("Creating five tasks") 14 | tasks = [asyncio.async(random_sleep(i)) for i in range(5)] 15 | print("Sleeping after starting five tasks") 16 | yield from asyncio.sleep(2) 17 | print("Waking and waiting for five tasks") 18 | yield from asyncio.wait(tasks) 19 | 20 | asyncio.get_event_loop().run_until_complete(five_sleepers()) 21 | print("Done five tasks") 22 | -------------------------------------------------------------------------------- /Chapter13/13_08_asyncio_dns.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from contextlib import suppress 3 | 4 | ip_map = { 5 | b'facebook.com.': '173.252.120.6', 6 | b'yougov.com.': '213.52.133.246', 7 | b'wipo.int.': '193.5.93.80' 8 | } 9 | 10 | def lookup_dns(data): 11 | domain = b'' 12 | pointer, part_length = 13, data[12] 13 | while part_length: 14 | domain += data[pointer:pointer+part_length] + b'.' 15 | pointer += part_length + 1 16 | part_length = data[pointer - 1] 17 | 18 | ip = ip_map.get(domain, '127.0.0.1') 19 | 20 | return domain, ip 21 | 22 | def create_response(data, ip): 23 | ba = bytearray 24 | packet = ba(data[:2]) + ba([129, 128]) + data[4:6] * 2 25 | packet += ba(4) + data[12:] 26 | packet += ba([192, 12, 0, 1, 0, 1, 0, 0, 0, 60, 0, 4]) 27 | for x in ip.split('.'): packet.append(int(x)) 28 | return packet 29 | 30 | class DNSProtocol(asyncio.DatagramProtocol): 31 | def connection_made(self, transport): 32 | self.transport = transport 33 | 34 | def datagram_received(self, data, addr): 35 | print("Received request from {}".format(addr[0])) 36 | domain, ip = lookup_dns(data) 37 | print("Sending IP {} for {} to {}".format( 38 | domain.decode(), ip, addr[0])) 39 | self.transport.sendto(create_response(data, ip), addr) 40 | 41 | 42 | loop = asyncio.get_event_loop() 43 | transport, protocol = loop.run_until_complete( 44 | loop.create_datagram_endpoint( 45 | DNSProtocol, local_addr=('127.0.0.1', 4343))) 46 | print("DNS Server running") 47 | 48 | with suppress(KeyboardInterrupt): 49 | loop.run_forever() 50 | transport.close() 51 | loop.close() 52 | 53 | # nslookup -port=4343 buchuki.com localhost 54 | -------------------------------------------------------------------------------- /Chapter13/13_09_async_multiprocessing.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | from concurrent.futures import ProcessPoolExecutor 4 | 5 | def sort_in_process(data): 6 | nums = json.loads(data.decode()) 7 | curr = 1 8 | while curr < len(nums): 9 | if nums[curr] >= nums[curr-1]: 10 | curr += 1 11 | else: 12 | nums[curr], nums[curr-1] = \ 13 | nums[curr-1], nums[curr] 14 | if curr > 1: 15 | curr -= 1 16 | 17 | return json.dumps(nums).encode() 18 | 19 | @asyncio.coroutine 20 | def sort_request(reader, writer): 21 | print("Received connection") 22 | length = yield from reader.read(8) 23 | data = yield from reader.readexactly(int.from_bytes(length, 'big')) 24 | result = yield from asyncio.get_event_loop().run_in_executor(None, sort_in_process, data) 25 | print("Sorted list") 26 | writer.write(result) 27 | writer.close() 28 | print("Connection closed") 29 | 30 | loop = asyncio.get_event_loop() 31 | loop.set_default_executor(ProcessPoolExecutor()) 32 | server = loop.run_until_complete( 33 | asyncio.start_server(sort_request, '127.0.0.1', 2015)) 34 | print("Sort Service running") 35 | 36 | loop.run_forever() 37 | server.close() 38 | loop.run_until_complete(server.wait_closed()) 39 | loop.close() 40 | -------------------------------------------------------------------------------- /Chapter13/13_10_async_client.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | import json 4 | 5 | @asyncio.coroutine 6 | def remote_sort(): 7 | reader, writer = yield from asyncio.open_connection('127.0.0.1', 2015) 8 | print("Generating random list...") 9 | numbers = [random.randrange(10000) for r in range(10000)] 10 | data = json.dumps(numbers).encode() 11 | print("List Generated, Sending data") 12 | writer.write(len(data).to_bytes(8, 'big')) 13 | writer.write(data) 14 | 15 | print("Waiting for data...") 16 | data = yield from reader.readexactly(len(data)) 17 | print("Received data") 18 | sorted_values = json.loads(data.decode()) 19 | print(sorted_values) 20 | print('\n') 21 | writer.close() 22 | 23 | loop = asyncio.get_event_loop() 24 | loop.run_until_complete(remote_sort()) 25 | loop.close() 26 | -------------------------------------------------------------------------------- /Chapter13/case_study/big.bmp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:68c8c29b6548cc6af6c801e9b324e9c4bf57ee1d07341c8a0e1f9f2df21600ae 3 | size 120960122 4 | -------------------------------------------------------------------------------- /Chapter13/case_study/big2.bmp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:68c8c29b6548cc6af6c801e9b324e9c4bf57ee1d07341c8a0e1f9f2df21600ae 3 | size 120960122 4 | -------------------------------------------------------------------------------- /Chapter13/case_study/big3.bmp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:68c8c29b6548cc6af6c801e9b324e9c4bf57ee1d07341c8a0e1f9f2df21600ae 3 | size 120960122 4 | -------------------------------------------------------------------------------- /Chapter13/case_study/big4.bmp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:68c8c29b6548cc6af6c801e9b324e9c4bf57ee1d07341c8a0e1f9f2df21600ae 3 | size 120960122 4 | -------------------------------------------------------------------------------- /Chapter13/case_study/big5.bmp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:68c8c29b6548cc6af6c801e9b324e9c4bf57ee1d07341c8a0e1f9f2df21600ae 3 | size 120960122 4 | -------------------------------------------------------------------------------- /Chapter13/case_study/big6.bmp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:68c8c29b6548cc6af6c801e9b324e9c4bf57ee1d07341c8a0e1f9f2df21600ae 3 | size 120960122 4 | -------------------------------------------------------------------------------- /Chapter13/case_study/big7.bmp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:68c8c29b6548cc6af6c801e9b324e9c4bf57ee1d07341c8a0e1f9f2df21600ae 3 | size 120960122 4 | -------------------------------------------------------------------------------- /Chapter13/case_study/big8.bmp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:68c8c29b6548cc6af6c801e9b324e9c4bf57ee1d07341c8a0e1f9f2df21600ae 3 | size 120960122 4 | -------------------------------------------------------------------------------- /Chapter13/case_study/bricks.bmp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e42a6abb2d9915c841300189cfafe30ddea27b2ee8df3134c42980abb475416f 3 | size 120122 4 | -------------------------------------------------------------------------------- /Chapter13/case_study/compress_bmp.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor 3 | 4 | from PIL import Image 5 | from bitarray import bitarray 6 | from pathlib import Path 7 | 8 | def compress_row(row): 9 | compressed = bytearray() 10 | chunks = split_bits(row, 127) 11 | for chunk in chunks: 12 | compressed.extend(compress_chunk(chunk)) 13 | return compressed 14 | 15 | def compress_chunk(chunk): 16 | compressed = bytearray() 17 | count = 1 18 | last = chunk[0] 19 | for bit in chunk[1:]: 20 | if bit != last: 21 | compressed.append(count | (128 * last)) 22 | count = 0 23 | last = bit 24 | count += 1 25 | compressed.append(count | (128 * last)) 26 | return compressed 27 | 28 | 29 | def split_bits(bits, width): 30 | for i in range(0, len(bits), width): 31 | yield bits[i:i+width] 32 | 33 | def compress_in_executor(executor, bits, width): 34 | row_compressors = [] 35 | for row in split_bits(bits, width): 36 | compressor = executor.submit(compress_row, row) 37 | row_compressors.append(compressor) 38 | 39 | compressed = bytearray() 40 | for compressor in row_compressors: 41 | compressed.extend(compressor.result()) 42 | return compressed 43 | 44 | def compress_image(in_filename, out_filename, executor=None): 45 | executor = executor if executor else ThreadPoolExecutor(4) 46 | with Image.open(in_filename) as image: 47 | bits = bitarray(image.convert('1').getdata()) 48 | width, height = image.size 49 | 50 | compressed = compress_in_executor(executor, bits, width) 51 | 52 | with open(out_filename, 'wb') as file: 53 | file.write(width.to_bytes(2, 'little')) 54 | file.write(height.to_bytes(2, 'little')) 55 | file.write(compressed) 56 | 57 | def compress_dir(in_dir, out_dir): 58 | if not out_dir.exists(): 59 | out_dir.mkdir() 60 | 61 | executor = ThreadPoolExecutor(4) 62 | for file in (f for f in in_dir.iterdir() if f.suffix == '.bmp'): 63 | out_file = (out_dir / file.name).with_suffix('.rle') 64 | executor.submit(compress_image, str(file), str(out_file)) 65 | 66 | def single_image_main(): 67 | in_filename, out_filename = sys.argv[1:3] 68 | executor = ThreadPoolExecutor(4) 69 | #executor = ProcessPoolExecutor() 70 | compress_image(in_filename, out_filename, executor) 71 | 72 | def dir_images_main(): 73 | in_dir, out_dir = (Path(p) for p in sys.argv[1:3]) 74 | compress_dir(in_dir, out_dir) 75 | 76 | if __name__ == '__main__': 77 | dir_images_main() 78 | #single_image_main() 79 | -------------------------------------------------------------------------------- /Chapter13/case_study/decompress_to_bmp.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | import sys 3 | 4 | def decompress(width, height, bytes): 5 | image = Image.new('1', (width, height)) 6 | 7 | col = 0 8 | row = 0 9 | for byte in bytes: 10 | color = (byte & 128) >> 7 11 | count = byte & ~128 12 | for i in range(count): 13 | image.putpixel((row, col), color) 14 | row += 1 15 | if not row % width: 16 | col += 1 17 | row = 0 18 | return image 19 | 20 | 21 | with open(sys.argv[1], 'rb') as file: 22 | width = int.from_bytes(file.read(2), 'little') 23 | height = int.from_bytes(file.read(2), 'little') 24 | 25 | image = decompress(width, height, file.read()) 26 | image.save(sys.argv[2], 'bmp') 27 | -------------------------------------------------------------------------------- /Chapter13/case_study/row.bmp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1e7b82cb4945f230f808b9b21ebc14dffb7e60be6fea68a877960d18ac80b5b7 3 | size 1322 4 | -------------------------------------------------------------------------------- /Chapter3/3_01_inheriting_from_object.py: -------------------------------------------------------------------------------- 1 | class MySubClass(object): 2 | pass 3 | -------------------------------------------------------------------------------- /Chapter3/3_02_simple_contact_class_to_inherit_from.py: -------------------------------------------------------------------------------- 1 | class Contact: 2 | all_contacts = [] 3 | 4 | def __init__(self, name, email): 5 | self.name = name 6 | self.email = email 7 | Contact.all_contacts.append(self) 8 | -------------------------------------------------------------------------------- /Chapter3/3_03_contact_inherit_supplier.py: -------------------------------------------------------------------------------- 1 | class Contact: 2 | all_contacts = [] 3 | 4 | def __init__(self, name, email): 5 | self.name = name 6 | self.email = email 7 | Contact.all_contacts.append(self) 8 | 9 | class Supplier(Contact): 10 | def order(self, order): 11 | print("If this were a real system we would send " 12 | "{} order to {}".format(order, self.name)) 13 | -------------------------------------------------------------------------------- /Chapter3/3_04_contact_list_inheritance.py: -------------------------------------------------------------------------------- 1 | class ContactList(list): 2 | def search(self, name): 3 | '''Return all contacts that contain the search value 4 | in their name.''' 5 | matching_contacts = [] 6 | for contact in self: 7 | if name in contact.name: 8 | matching_contacts.append(contact) 9 | return matching_contacts 10 | 11 | class Contact: 12 | all_contacts = ContactList() 13 | 14 | def __init__(self, name, email): 15 | self.name = name 16 | self.email = email 17 | self.all_contacts.append(self) 18 | -------------------------------------------------------------------------------- /Chapter3/3_05_dictionary_long_name.py: -------------------------------------------------------------------------------- 1 | class LongNameDict(dict): 2 | def longest_key(self): 3 | longest = None 4 | for key in self: 5 | if not longest or len(key) > len(longest): 6 | longest = key 7 | return longest 8 | -------------------------------------------------------------------------------- /Chapter3/3_06_friend_overrides_init.py: -------------------------------------------------------------------------------- 1 | class Contact: 2 | all_contacts = [] 3 | 4 | def __init__(self, name, email): 5 | self.name = name 6 | self.email = email 7 | Contact.all_contacts.append(self) 8 | 9 | class Friend(Contact): 10 | def __init__(self, name, email, phone): 11 | self.name = name 12 | self.email = email 13 | self.phone = phone 14 | -------------------------------------------------------------------------------- /Chapter3/3_07_friend_overrides_init_super.py: -------------------------------------------------------------------------------- 1 | class Contact: 2 | all_contacts = [] 3 | 4 | def __init__(self, name, email): 5 | self.name = name 6 | self.email = email 7 | Contact.all_contacts.append(self) 8 | 9 | class Friend(Contact): 10 | def __init__(self, name, email, phone): 11 | super().__init__(name, email) 12 | self.phone = phone 13 | -------------------------------------------------------------------------------- /Chapter3/3_08_send_mail.py: -------------------------------------------------------------------------------- 1 | class MailSender: 2 | def send_mail(self, message): 3 | print("Sending mail to " + self.email) 4 | # Add e-mail logic here 5 | 6 | class EmailableContact(Contact, MailSender): 7 | pass 8 | -------------------------------------------------------------------------------- /Chapter3/3_09_send_mail_multi.py: -------------------------------------------------------------------------------- 1 | class MailSender: 2 | def send_mail(self, message): 3 | print("Sending mail to " + self.email) 4 | # Add e-mail logic here 5 | 6 | class EmailableContact(Friend, MailSender): 7 | pass 8 | -------------------------------------------------------------------------------- /Chapter3/3_10_friend_address_holder.py: -------------------------------------------------------------------------------- 1 | class AddressHolder: 2 | def __init__(self, street, city, state, code): 3 | self.street = street 4 | self.city = city 5 | self.state = state 6 | self.code = code 7 | -------------------------------------------------------------------------------- /Chapter3/3_11_friend_multi.py: -------------------------------------------------------------------------------- 1 | class Friend(Contact, AddressHolder): 2 | def __init__(self, name, email, phone, 3 | street, city, state, code): 4 | Contact.__init__(self, name, email) 5 | AddressHolder.__init__( 6 | self, street, city, state, code) 7 | self.phone = phone 8 | -------------------------------------------------------------------------------- /Chapter3/3_12_contrived_diamond.py: -------------------------------------------------------------------------------- 1 | class BaseClass: 2 | num_base_calls = 0 3 | def call_me(self): 4 | print("Calling method on Base Class") 5 | self.num_base_calls += 1 6 | 7 | class LeftSubclass(BaseClass): 8 | num_left_calls = 0 9 | def call_me(self): 10 | BaseClass.call_me(self) 11 | print("Calling method on Left Subclass") 12 | self.num_left_calls += 1 13 | 14 | class RightSubclass(BaseClass): 15 | num_right_calls = 0 16 | def call_me(self): 17 | BaseClass.call_me(self) 18 | print("Calling method on Right Subclass") 19 | self.num_right_calls += 1 20 | 21 | class Subclass(LeftSubclass, RightSubclass): 22 | num_sub_calls = 0 23 | def call_me(self): 24 | LeftSubclass.call_me(self) 25 | RightSubclass.call_me(self) 26 | print("Calling method on Subclass") 27 | self.num_sub_calls += 1 28 | -------------------------------------------------------------------------------- /Chapter3/3_14_contrived_diamond_super.py: -------------------------------------------------------------------------------- 1 | class BaseClass: 2 | num_base_calls = 0 3 | def call_me(self): 4 | print("Calling method on Base Class") 5 | self.num_base_calls += 1 6 | 7 | class LeftSubclass(BaseClass): 8 | num_left_calls = 0 9 | def call_me(self): 10 | super().call_me() 11 | print("Calling method on Left Subclass") 12 | self.num_left_calls += 1 13 | 14 | class RightSubclass(BaseClass): 15 | num_right_calls = 0 16 | def call_me(self): 17 | super().call_me() 18 | print("Calling method on Right Subclass") 19 | self.num_right_calls += 1 20 | 21 | class Subclass(LeftSubclass, RightSubclass): 22 | num_sub_calls = 0 23 | def call_me(self): 24 | super().call_me() 25 | print("Calling method on Subclass") 26 | self.num_sub_calls += 1 27 | -------------------------------------------------------------------------------- /Chapter3/3_15_friend_multi_super.py: -------------------------------------------------------------------------------- 1 | class Contact: 2 | all_contacts = [] 3 | 4 | def __init__(self, name='', email='', **kwargs): 5 | super().__init__(**kwargs) 6 | self.name = name 7 | self.email = email 8 | self.all_contacts.append(self) 9 | 10 | class AddressHolder: 11 | def __init__(self, street='', city='', state='', code='', **kwargs): 12 | super().__init__(**kwargs) 13 | self.street = street 14 | self.city = city 15 | self.state = state 16 | self.code = code 17 | 18 | class Friend(Contact, AddressHolder): 19 | def __init__(self, phone='', **kwargs): 20 | print(kwargs) 21 | super().__init__(**kwargs) 22 | self.phone = phone 23 | -------------------------------------------------------------------------------- /Chapter3/3_16_polymorphic_audio.py: -------------------------------------------------------------------------------- 1 | class AudioFile: 2 | def __init__(self, filename): 3 | if not filename.endswith(self.ext): 4 | raise Exception("Invalid file format") 5 | 6 | self.filename = filename 7 | 8 | class MP3File(AudioFile): 9 | ext = "mp3" 10 | def play(self): 11 | print("playing {} as mp3".format(self.filename)) 12 | 13 | class WavFile(AudioFile): 14 | ext = "wav" 15 | def play(self): 16 | print("playing {} as wav".format(self.filename)) 17 | 18 | class OggFile(AudioFile): 19 | ext = "ogg" 20 | def play(self): 21 | print("playing {} as ogg".format(self.filename)) 22 | -------------------------------------------------------------------------------- /Chapter3/3_17.1_abc_container.py: -------------------------------------------------------------------------------- 1 | 2 | class OddContainer: 3 | def __contains__(self, x): 4 | if not isinstance(x, int) or not x % 2: 5 | return False 6 | return True 7 | 8 | -------------------------------------------------------------------------------- /Chapter3/3_17.2_abc_media.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | class MediaLoader(metaclass=abc.ABCMeta): 4 | @abc.abstractmethod 5 | def play(self): 6 | pass 7 | 8 | @abc.abstractproperty 9 | def ext(self): 10 | pass 11 | 12 | @classmethod 13 | def __subclasshook__(cls, C): 14 | if cls is MediaLoader: 15 | attrs = set(dir(C)) 16 | if set(cls.__abstractmethods__) <= attrs: 17 | return True 18 | 19 | return NotImplemented 20 | -------------------------------------------------------------------------------- /Chapter3/3_17_ducktype_flac.py: -------------------------------------------------------------------------------- 1 | class FlacFile: 2 | def __init__(self, filename): 3 | if not filename.endswith(".flac"): 4 | raise Exception("Invalid file format") 5 | 6 | self.filename = filename 7 | 8 | def play(self): 9 | print("playing {} as flac movie".format(self.filename)) 10 | -------------------------------------------------------------------------------- /Chapter3/3_18_property.py: -------------------------------------------------------------------------------- 1 | class Property: 2 | def __init__(self, square_feet='', beds='', 3 | baths='', **kwargs): 4 | super().__init__(**kwargs) 5 | self.square_feet = square_feet 6 | self.num_bedrooms = beds 7 | self.num_baths = baths 8 | 9 | def display(self): 10 | print("PROPERTY DETAILS") 11 | print("================") 12 | print("square footage: {}".format(self.square_feet)) 13 | print("bedrooms: {}".format(self.num_bedrooms)) 14 | print("bathrooms: {}".format(self.num_baths)) 15 | print() 16 | 17 | def prompt_init(): 18 | return dict(square_feet=input("Enter the square feet: "), 19 | beds=input("Enter number of bedrooms: "), 20 | baths=input("Enter number of baths: ")) 21 | prompt_init = staticmethod(prompt_init) 22 | 23 | -------------------------------------------------------------------------------- /Chapter3/3_19_apartment_ugly_prompt.py: -------------------------------------------------------------------------------- 1 | class Apartment(Property): 2 | valid_laundries = ("coin", "ensuite", "none") 3 | valid_balconies = ("yes", "no", "solarium") 4 | 5 | def __init__(self, balcony='', laundry='', **kwargs): 6 | super().__init__(**kwargs) 7 | self.balcony = balcony 8 | self.laundry = laundry 9 | 10 | def display(self): 11 | super().display() 12 | print("APARTMENT DETAILS") 13 | print("laundry: {}".format(self.laundry)) 14 | print("has balcony: {}".format(self.balcony)) 15 | 16 | def prompt_init(): 17 | parent_init = Property.prompt_init() 18 | laundry = '' 19 | while laundry.lower() not in \ 20 | Apartment.valid_laundries: 21 | laundry = input("What laundry facilities does " 22 | "the property have? ({})".format( 23 | ", ".join(Apartment.valid_laundries)) 24 | balcony = '' 25 | while balcony.lower() not in \ 26 | Apartment.valid_balconies: 27 | balcony = input( 28 | "Does the property have a balcony? " 29 | "({})".format( 30 | ", ".join(Apartment.valid_balconies)) 31 | parent_init.update({ 32 | "laundry": laundry, 33 | "balcony": balcony 34 | }) 35 | return parent_init 36 | prompt_init = staticmethod(prompt_init) 37 | -------------------------------------------------------------------------------- /Chapter3/3_20_validation_function.py: -------------------------------------------------------------------------------- 1 | def get_valid_input(input_string, valid_options): 2 | input_string += " ({}) ".format(", ".join(valid_options)) 3 | response = input(input_string) 4 | while response.lower() not in valid_options: 5 | response = input(input_string) 6 | return response 7 | -------------------------------------------------------------------------------- /Chapter3/3_21_apartment_nice_prompt.py: -------------------------------------------------------------------------------- 1 | class Apartment(Property): 2 | valid_laundries = ("coin", "ensuite", "none") 3 | valid_balconies = ("yes", "no", "solarium") 4 | 5 | def __init__(self, balcony='', laundry='', **kwargs): 6 | super().__init__(**kwargs) 7 | self.balcony = balcony 8 | self.laundry = laundry 9 | 10 | def display(self): 11 | super().display() 12 | print("APARTMENT DETAILS") 13 | print("laundry: {}".format(self.laundry)) 14 | print("has balcony: {}".format(self.balcony)) 15 | 16 | def prompt_init(): 17 | parent_init = Property.prompt_init() 18 | laundry = get_valid_input( 19 | "What laundry facilities does " 20 | "the property have? ", 21 | Apartment.valid_laundries) 22 | balcony = get_valid_input( 23 | "Does the property have a balcony? ", 24 | Apartment.valid_balconies) 25 | parent_init.update({ 26 | "laundry": laundry, 27 | "balcony": balcony 28 | }) 29 | return parent_init 30 | prompt_init = staticmethod(prompt_init) 31 | -------------------------------------------------------------------------------- /Chapter3/3_22_house.py: -------------------------------------------------------------------------------- 1 | class House(Property): 2 | valid_garage = ("attached", "detached", "none") 3 | valid_fenced = ("yes", "no") 4 | 5 | def __init__(self, num_stories='', 6 | garage='', fenced='', **kwargs): 7 | super().__init__(**kwargs) 8 | self.garage = garage 9 | self.fenced = fenced 10 | self.num_stories = num_stories 11 | 12 | def display(self): 13 | super().display() 14 | print("HOUSE DETAILS") 15 | print("# of stories: {}".format(self.num_stories)) 16 | print("garage: {}".format(self.garage)) 17 | print("fenced yard: {}".format(self.fenced)) 18 | 19 | def prompt_init(): 20 | parent_init = Property.prompt_init() 21 | fenced = get_valid_input("Is the yard fenced? ", 22 | House.valid_fenced) 23 | garage = get_valid_input("Is there a garage? ", 24 | House.valid_garage) 25 | num_stories = input("How many stories? ") 26 | 27 | parent_init.update({ 28 | "fenced": fenced, 29 | "garage": garage, 30 | "num_stories": num_stories 31 | }) 32 | return parent_init 33 | prompt_init = staticmethod(prompt_init) 34 | -------------------------------------------------------------------------------- /Chapter3/3_23_purchase_and_rental.py: -------------------------------------------------------------------------------- 1 | class Purchase: 2 | def __init__(self, price='', taxes='', **kwargs): 3 | super().__init__(**kwargs) 4 | self.price = price 5 | self.taxes = taxes 6 | 7 | def display(self): 8 | super().display() 9 | print("PURCHASE DETAILS") 10 | print("selling price: {}".format(self.price)) 11 | print("estimated taxes: {}".format(self.taxes)) 12 | 13 | def prompt_init(): 14 | return dict( 15 | price=input("What is the selling price? "), 16 | taxes=input("What are the estimated taxes? ")) 17 | prompt_init = staticmethod(prompt_init) 18 | 19 | class Rental: 20 | def __init__(self, furnished='', utilities='', 21 | rent='', **kwargs): 22 | super().__init__(**kwargs) 23 | self.furnished = furnished 24 | self.rent = rent 25 | self.utilities = utilities 26 | 27 | def display(self): 28 | super().display() 29 | print("RENTAL DETAILS") 30 | print("rent: {}".format(self.rent)) 31 | print("estimated utilities: {}".format( 32 | self.utilities)) 33 | print("furnished: {}".format(self.furnished)) 34 | 35 | def prompt_init(): 36 | return dict( 37 | rent=input("What is the monthly rent? "), 38 | utilities=input( 39 | "What are the estimated utilities? "), 40 | furnished = get_valid_input( 41 | "Is the property furnished? ", 42 | ("yes", "no"))) 43 | prompt_init = staticmethod(prompt_init) 44 | -------------------------------------------------------------------------------- /Chapter3/3_24_house_rental.py: -------------------------------------------------------------------------------- 1 | class HouseRental(Rental, House): 2 | def prompt_init(): 3 | init = House.prompt_init() 4 | init.update(Rental.prompt_init()) 5 | return init 6 | prompt_init = staticmethod(prompt_init) 7 | 8 | -------------------------------------------------------------------------------- /Chapter3/3_25_remaining_subclasses.py: -------------------------------------------------------------------------------- 1 | class ApartmentRental(Rental, Apartment): 2 | def prompt_init(): 3 | init = Apartment.prompt_init() 4 | init.update(Rental.prompt_init()) 5 | return init 6 | prompt_init = staticmethod(prompt_init) 7 | 8 | class ApartmentPurchase(Purchase, Apartment): 9 | def prompt_init(): 10 | init = Apartment.prompt_init() 11 | init.update(Purchase.prompt_init()) 12 | return init 13 | prompt_init = staticmethod(prompt_init) 14 | 15 | class HousePurchase(Purchase, House): 16 | def prompt_init(): 17 | init = House.prompt_init() 18 | init.update(Purchase.prompt_init()) 19 | return init 20 | prompt_init = staticmethod(prompt_init) 21 | -------------------------------------------------------------------------------- /Chapter3/3_26_rudimentary_agent.py: -------------------------------------------------------------------------------- 1 | class Agent: 2 | def __init__(self): 3 | self.property_list = [] 4 | 5 | def display_properties(): 6 | for property in self.property_list: 7 | property.display() 8 | -------------------------------------------------------------------------------- /Chapter3/3_27_type_map.py: -------------------------------------------------------------------------------- 1 | class Agent: 2 | type_map = { 3 | ("house", "rental"): HouseRental, 4 | ("house", "purchase"): HousePurchase, 5 | ("apartment", "rental"): ApartmentRental, 6 | ("apartment", "purchase"): ApartmentPurchase 7 | } 8 | 9 | def __init__(self): 10 | self.property_list = [] 11 | 12 | def display_properties(): 13 | for property in self.property_list: 14 | property.display() 15 | -------------------------------------------------------------------------------- /Chapter3/3_28_add_property.py: -------------------------------------------------------------------------------- 1 | class Agent: 2 | type_map = { 3 | ("house", "rental"): HouseRental, 4 | ("house", "purchase"): HousePurchase, 5 | ("apartment", "rental"): ApartmentRental, 6 | ("apartment", "purchase"): ApartmentPurchase 7 | } 8 | 9 | def __init__(self): 10 | self.property_list = [] 11 | 12 | def display_properties(): 13 | for property in self.property_list: 14 | property.display() 15 | 16 | def add_property(self): 17 | property_type = get_valid_input( 18 | "What type of property? ", 19 | ("house", "apartment")).lower() 20 | payment_type = get_valid_input( 21 | "What payment type? ", 22 | ("purchase", "rental")).lower() 23 | 24 | PropertyClass = self.type_map[(property_type, payment_type)] 25 | init_args = PropertyClass.prompt_init() 26 | self.property_list.append(PropertyClass(**init_args)) 27 | -------------------------------------------------------------------------------- /Chapter4/04_03_method_calls_excepting.py: -------------------------------------------------------------------------------- 1 | def no_return(): 2 | print("I am about to raise an exception") 3 | raise Exception("This is always raised") 4 | print("This line will never execute") 5 | return "I won't be returned" 6 | 7 | def call_exceptor(): 8 | print("call_exceptor starts here...") 9 | no_return() 10 | print("an exception was raised...") 11 | print("...so these lines don't run") 12 | -------------------------------------------------------------------------------- /Chapter4/4_01_even_integers.py: -------------------------------------------------------------------------------- 1 | class EvenOnly(list): 2 | def append(self, integer): 3 | if not isinstance(integer, int): 4 | raise TypeError("Only integers can be added") 5 | if integer % 2: 6 | raise ValueError("Only even numbers can be added") 7 | super().append(integer) 8 | -------------------------------------------------------------------------------- /Chapter4/4_02_exception_quits.py: -------------------------------------------------------------------------------- 1 | def no_return(): 2 | print("I am about to raise an exception") 3 | raise Exception("This is always raised") 4 | print("This line will never execute") 5 | return "I won't be returned" 6 | 7 | -------------------------------------------------------------------------------- /Chapter4/4_04_try_except.py: -------------------------------------------------------------------------------- 1 | def no_return(): 2 | print("I am about to raise an exception") 3 | raise Exception("This is always raised") 4 | print("This line will never execute") 5 | return "I won't be returned" 6 | 7 | try: 8 | no_return() 9 | except: 10 | print("I caught an exception") 11 | print("executed after the exception") 12 | -------------------------------------------------------------------------------- /Chapter4/4_05_catch_specific_exception.py: -------------------------------------------------------------------------------- 1 | def funny_division(anumber): 2 | try: 3 | return 100 / anumber 4 | except ZeroDivisionError: 5 | return "Silly wabbit, you can't divide by zero!" 6 | 7 | print(funny_division(0)) 8 | print(funny_division(50.0)) 9 | print(funny_division("hello")) 10 | -------------------------------------------------------------------------------- /Chapter4/4_06_catch_multiple_exceptions.py: -------------------------------------------------------------------------------- 1 | def funny_division2(anumber): 2 | try: 3 | if anumber == 13: 4 | raise ValueError("13 is an unlucky number") 5 | return 100 / anumber 6 | except (ZeroDivisionError, TypeError): 7 | return "Enter a number other than zero" 8 | 9 | for val in (0, "hello", 50.0, 13): 10 | print("Testing {}:".format(val), end=" ") 11 | print(funny_division2(val)) 12 | -------------------------------------------------------------------------------- /Chapter4/4_07_catch_multiple_different.py: -------------------------------------------------------------------------------- 1 | def funny_division3(anumber): 2 | try: 3 | if anumber == 13: 4 | raise ValueError("13 is an unlucky number") 5 | return 100 / anumber 6 | except ZeroDivisionError: 7 | return "Enter a number other than zero" 8 | except TypeError: 9 | return "Enter a numerical value" 10 | except ValueError: 11 | print("No, No, not 13!") 12 | raise 13 | 14 | for val in (0, "hello", 50.0, 13): 15 | print("Testing %s:" % val, end=" ") 16 | print(funny_division3(val)) 17 | -------------------------------------------------------------------------------- /Chapter4/4_08_catch_as_keyword.py: -------------------------------------------------------------------------------- 1 | try: 2 | raise ValueError("This is an argument") 3 | except ValueError as e: 4 | print("The exception arguments were", e.args) 5 | -------------------------------------------------------------------------------- /Chapter4/4_09_finally_and_else.py: -------------------------------------------------------------------------------- 1 | import random 2 | some_exceptions = [ValueError, TypeError, IndexError, None] 3 | 4 | try: 5 | choice = random.choice(some_exceptions) 6 | print("raising {}".format(choice)) 7 | if choice: 8 | raise choice("An error") 9 | except ValueError: 10 | print("Caught a ValueError") 11 | except TypeError: 12 | print("Caught a TypeError") 13 | except Exception as e: 14 | print("Caught some other error: %s" % e.__class__.__name__) 15 | else: 16 | print("This code called if there is no exception") 17 | finally: 18 | print("This cleanup code is always called") 19 | -------------------------------------------------------------------------------- /Chapter4/4_10_defining_an_exception.py: -------------------------------------------------------------------------------- 1 | class InvalidWithdrawal(Exception): 2 | pass 3 | 4 | raise InvalidWithdrawal("You don't have $50 in your account") 5 | -------------------------------------------------------------------------------- /Chapter4/4_11_exception_with_custom_args.py: -------------------------------------------------------------------------------- 1 | class InvalidWithdrawal(Exception): 2 | def __init__(self, balance, amount): 3 | super().__init__("account doesn't have ${}".format( 4 | amount)) 5 | self.amount = amount 6 | self.balance = balance 7 | 8 | def overage(self): 9 | return self.amount - self.balance 10 | 11 | raise InvalidWithdrawal(25, 50) 12 | -------------------------------------------------------------------------------- /Chapter4/4_12_handle_custom_exception.py: -------------------------------------------------------------------------------- 1 | class InvalidWithdrawal(Exception): 2 | def __init__(self, balance, amount): 3 | super().__init__("account doesn't have ${}".format( 4 | amount)) 5 | self.amount = amount 6 | self.balance = balance 7 | 8 | def overage(self): 9 | return self.amount - self.balance 10 | 11 | try: 12 | raise InvalidWithdrawal(25, 50) 13 | except InvalidWithdrawal as e: 14 | print("I'm sorry, but your withdrawal is " 15 | "more than your balance by " 16 | "${}".format(e.overage())) 17 | -------------------------------------------------------------------------------- /Chapter4/4_13_branching_vs_exceptions.py: -------------------------------------------------------------------------------- 1 | def divide_with_exception(number, divisor): 2 | try: 3 | print("{} / {} = {}".format( 4 | number, divisor, number / divisor * 1.0)) 5 | except ZeroDivisionError: 6 | print("You can't divide by zero") 7 | 8 | def divide_with_if(number, divisor): 9 | if divisor == 0: 10 | print("You can't divide by zero") 11 | else: 12 | print("{} / {} = {}".format( 13 | number, divisor, number / divisor * 1.0)) 14 | 15 | divide_with_exception(10, 5) 16 | divide_with_if(10, 5) 17 | divide_with_if(10, 0) 18 | divide_with_exception(10, 0) 19 | -------------------------------------------------------------------------------- /Chapter4/4_14_inventory_mock_object.py: -------------------------------------------------------------------------------- 1 | class Inventory: 2 | def lock(self, item_type): 3 | '''Select the type of item that is going to 4 | be manipulated. This method will lock the 5 | item so nobody else can manipulate the 6 | inventory until it's returned. This prevents 7 | selling the same item to two different 8 | customers.''' 9 | pass 10 | 11 | def unlock(self, item_type): 12 | '''Release the given type so that other 13 | customers can access it.''' 14 | pass 15 | 16 | def purchase(self, item_type): 17 | '''If the item is not locked, raise an 18 | exception. If the itemtype does not exist, 19 | raise an exception. If the item is currently 20 | out of stock, raise an exception. If the item 21 | is available, subtract one item and return 22 | the number of items left.''' 23 | pass 24 | 25 | -------------------------------------------------------------------------------- /Chapter4/4_15_inventory_handling.py: -------------------------------------------------------------------------------- 1 | class Inventory: 2 | def lock(self, item_type): 3 | pass 4 | 5 | def unlock(self, item_type): 6 | pass 7 | 8 | def purchase(self, item_type): 9 | pass 10 | 11 | item_type = 'widget' 12 | inv = Inventory() 13 | inv.lock(item_type) 14 | try: 15 | num_left = inv.purchase(item_type) 16 | except InvalidItemType: 17 | print("Sorry, we don't sell {}".format(item_type)) 18 | except OutOfStock: 19 | print("Sorry, that item is out of stock.") 20 | else: 21 | print("Purchase complete. There are " 22 | "{} {}s left".format(num_left, item_type)) 23 | finally: 24 | inv.unlock() 25 | -------------------------------------------------------------------------------- /Chapter4/4_16_auth_user.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | class User: 4 | def __init__(self, username, password): 5 | '''Create a new user object. The password 6 | will be encrypted before storing.''' 7 | self.username = username 8 | self.password = self._encrypt_pw(password) 9 | self.is_logged_in = False 10 | 11 | def _encrypt_pw(self, password): 12 | '''Encrypt the password with the username and return 13 | the sha digest.''' 14 | hash_string = (self.username + password) 15 | hash_string = hash_string.encode("utf8") 16 | return hashlib.sha256(hash_string).hexdigest() 17 | 18 | def check_password(self, password): 19 | '''Return True if the password is valid for this 20 | user, false otherwise.''' 21 | encrypted = self._encrypt_pw(password) 22 | return encrypted == self.password 23 | 24 | 25 | class AuthException(Exception): 26 | def __init__(self, username, user=None): 27 | super().__init__(username) 28 | self.username = username 29 | self.user = user 30 | 31 | class UsernameAlreadyExists(AuthException): 32 | pass 33 | 34 | class PasswordTooShort(AuthException): 35 | pass 36 | -------------------------------------------------------------------------------- /Chapter4/4_17_authenticator.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | class User: 4 | def __init__(self, username, password): 5 | '''Create a new user object. The password 6 | will be encrypted before storing.''' 7 | self.username = username 8 | self.password = self._encrypt_pw(password) 9 | self.is_logged_in = False 10 | 11 | def _encrypt_pw(self, password): 12 | '''Encrypt the password with the username and return 13 | the sha digest.''' 14 | hash_string = (self.username + password) 15 | hash_string = hash_string.encode("utf8") 16 | return hashlib.sha256(hash_string).hexdigest() 17 | 18 | def check_password(self, password): 19 | '''Return True if the password is valid for this 20 | user, false otherwise.''' 21 | encrypted = self._encrypt_pw(password) 22 | return encrypted == self.password 23 | 24 | 25 | class AuthException(Exception): 26 | def __init__(self, username, user=None): 27 | super().__init__(username) 28 | self.username = username 29 | self.user = user 30 | 31 | class UsernameAlreadyExists(AuthException): 32 | pass 33 | 34 | class PasswordTooShort(AuthException): 35 | pass 36 | 37 | class Authenticator: 38 | def __init__(self): 39 | '''Construct an authenticator to manage 40 | users logging in and out.''' 41 | self.users = {} 42 | 43 | def add_user(self, username, password): 44 | if username in self.users: 45 | raise UsernameAlreadyExists(username) 46 | if len(password) < 6: 47 | raise PasswordTooShort(username) 48 | self.users[username] = User(username, password) 49 | -------------------------------------------------------------------------------- /Chapter4/4_18_login.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | class User: 4 | def __init__(self, username, password): 5 | '''Create a new user object. The password 6 | will be encrypted before storing.''' 7 | self.username = username 8 | self.password = self._encrypt_pw(password) 9 | self.is_logged_in = False 10 | 11 | def _encrypt_pw(self, password): 12 | '''Encrypt the password with the username and return 13 | the sha digest.''' 14 | hash_string = (self.username + password) 15 | hash_string = hash_string.encode("utf8") 16 | return hashlib.sha256(hash_string).hexdigest() 17 | 18 | def check_password(self, password): 19 | '''Return True if the password is valid for this 20 | user, false otherwise.''' 21 | encrypted = self._encrypt_pw(password) 22 | return encrypted == self.password 23 | 24 | 25 | class AuthException(Exception): 26 | def __init__(self, username, user=None): 27 | super().__init__(username) 28 | self.username = username 29 | self.user = user 30 | 31 | class UsernameAlreadyExists(AuthException): 32 | pass 33 | 34 | class PasswordTooShort(AuthException): 35 | pass 36 | 37 | class InvalidUsername(AuthException): 38 | pass 39 | 40 | class InvalidPassword(AuthException): 41 | pass 42 | 43 | class Authenticator: 44 | def __init__(self): 45 | '''Construct an authenticator to manage 46 | users logging in and out.''' 47 | self.users = {} 48 | 49 | def add_user(self, username, password): 50 | if username in self.users: 51 | raise UsernameAlreadyExists(username) 52 | if len(password) < 6: 53 | raise PasswordTooShort(username) 54 | self.users[username] = User(username, password) 55 | 56 | def login(self, username, password): 57 | try: 58 | user = self.users[username] 59 | except KeyError: 60 | raise InvalidUsername(username) 61 | 62 | if not user.check_password(password): 63 | raise InvalidPassword(username, user) 64 | 65 | user.is_logged_in = True 66 | return True 67 | 68 | def is_logged_in(self, username): 69 | if username in self.users: 70 | return self.users[username].is_logged_in 71 | return False 72 | 73 | authenticator = Authenticator() 74 | -------------------------------------------------------------------------------- /Chapter4/4_20_test_auth.py: -------------------------------------------------------------------------------- 1 | import auth 2 | 3 | # Set up a test user and permission 4 | auth.authenticator.add_user("joe", "joepassword") 5 | auth.authorizor.add_permission("test program") 6 | auth.authorizor.add_permission("change program") 7 | auth.authorizor.permit_user("test program", "joe") 8 | 9 | class Editor: 10 | def __init__(self): 11 | self.username = None 12 | self.menu_map = { 13 | "login": self.login, 14 | "test": self.test, 15 | "change": self.change, 16 | "quit": self.quit 17 | } 18 | 19 | def login(self): 20 | logged_in = False 21 | while not logged_in: 22 | username = input("username: ") 23 | password = input("password: ") 24 | try: 25 | logged_in = auth.authenticator.login( 26 | username, password) 27 | except auth.InvalidUsername: 28 | print("Sorry, that username does not exist") 29 | except auth.InvalidPassword: 30 | print("Sorry, incorrect password") 31 | else: 32 | self.username = username 33 | 34 | def is_permitted(self, permission): 35 | try: 36 | auth.authorizor.check_permission( 37 | permission, self.username) 38 | except auth.NotLoggedInError as e: 39 | print("{} is not logged in".format(e.username)) 40 | return False 41 | except auth.NotPermittedError as e: 42 | print("{} cannot {}".format( 43 | e.username, permission)) 44 | return False 45 | else: 46 | return True 47 | 48 | def test(self): 49 | if self.is_permitted("test program"): 50 | print("Testing program now...") 51 | 52 | def change(self): 53 | if self.is_permitted("change program"): 54 | print("Changing program now...") 55 | 56 | def quit(self): 57 | raise SystemExit() 58 | 59 | def menu(self): 60 | try: 61 | answer = "" 62 | while True: 63 | print(""" 64 | Please enter a command: 65 | \tlogin\tLogin 66 | \ttest\tTest the program 67 | \tchange\tChange the program 68 | \tquit\tQuit 69 | """) 70 | answer = input("enter a command: ").lower() 71 | try: 72 | func = self.menu_map[answer] 73 | except KeyError: 74 | print("{} is not a valid option".format( 75 | answer)) 76 | else: 77 | func() 78 | finally: 79 | print("Thank you for testing the auth module") 80 | 81 | 82 | Editor().menu() 83 | -------------------------------------------------------------------------------- /Chapter4/__pycache__/auth.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter4/__pycache__/auth.cpython-34.pyc -------------------------------------------------------------------------------- /Chapter4/auth.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter4/auth.pyc -------------------------------------------------------------------------------- /Chapter5/5_01_distances_no_objects.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | def distance(p1, p2): 4 | return math.sqrt((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2) 5 | 6 | def perimeter(polygon): 7 | perimeter = 0 8 | points = polygon + [polygon[0]] 9 | for i in range(len(polygon)): 10 | perimeter += distance(points[i], points[i+1]) 11 | return perimeter 12 | -------------------------------------------------------------------------------- /Chapter5/5_02_distances_by_object.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | class Point: 4 | def __init__(self, x, y): 5 | self.x = x 6 | self.y = y 7 | 8 | def distance(self, p2): 9 | return math.sqrt((self.x-p2.x)**2 + (self.y-p2.y)**2) 10 | 11 | class Polygon: 12 | def __init__(self): 13 | self.vertices = [] 14 | 15 | def add_point(self, point): 16 | self.vertices.append((point)) 17 | 18 | def perimeter(self): 19 | perimeter = 0 20 | points = self.vertices + [self.vertices[0]] 21 | for i in range(len(self.vertices)): 22 | perimeter += points[i].distance(points[i+1]) 23 | return perimeter 24 | -------------------------------------------------------------------------------- /Chapter5/5_03_object_polygon_init.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | class Point: 4 | def __init__(self, x, y): 5 | self.x = x 6 | self.y = y 7 | 8 | def distance(self, p2): 9 | return math.sqrt((self.x-p2.x)**2 + (self.y-p2.y)**2) 10 | 11 | class Polygon: 12 | def __init__(self, points = []): 13 | self.vertices = [] 14 | for point in points: 15 | if isinstance(point, tuple): 16 | point = Point(*point) 17 | self.vertices.append(point) 18 | 19 | def add_point(self, point): 20 | self.vertices.append((point)) 21 | 22 | def perimeter(self): 23 | perimeter = 0 24 | points = self.vertices + [self.vertices[0]] 25 | for i in range(len(self.vertices)): 26 | perimeter += points[i].distance(points[i+1]) 27 | return perimeter 28 | -------------------------------------------------------------------------------- /Chapter5/5_04_pytho_ugly_as_java.py: -------------------------------------------------------------------------------- 1 | class Color: 2 | def __init__(self, rgb_value, name): 3 | self._rgb_value = rgb_value 4 | self._name = name 5 | 6 | def set_name(self, name): 7 | self._name = name 8 | 9 | def get_name(self): 10 | return self._name 11 | -------------------------------------------------------------------------------- /Chapter5/5_05_python_pretty_as_python.py: -------------------------------------------------------------------------------- 1 | class Color: 2 | def __init__(self, rgb_value, name): 3 | self.rgb_value = rgb_value 4 | self.name = name 5 | 6 | c = Color("#ff0000", "bright red") 7 | print(c.name) 8 | c.name = "red" 9 | -------------------------------------------------------------------------------- /Chapter5/5_06_setting_name_in_method.py: -------------------------------------------------------------------------------- 1 | class Color: 2 | def __init__(self, rgb_value, name): 3 | self._rgb_value = rgb_value 4 | self._name = name 5 | 6 | def set_name(self, name): 7 | if not name: 8 | raise Exception("Invalid Name") 9 | self._name = name 10 | 11 | def get_name(self): 12 | return self._name 13 | -------------------------------------------------------------------------------- /Chapter5/5_07_setting_name_property.py: -------------------------------------------------------------------------------- 1 | class Color: 2 | def __init__(self, rgb_value, name): 3 | self.rgb_value = rgb_value 4 | self._name = name 5 | 6 | def _set_name(self, name): 7 | if not name: 8 | raise Exception("Invalid Name") 9 | self._name = name 10 | 11 | def _get_name(self): 12 | return self._name 13 | 14 | name = property(_get_name, _set_name) 15 | -------------------------------------------------------------------------------- /Chapter5/5_08_property_arguments.py: -------------------------------------------------------------------------------- 1 | class Silly: 2 | def _get_silly(self): 3 | print("You are getting silly") 4 | return self._silly 5 | def _set_silly(self, value): 6 | print("You are making silly {}".format(value)) 7 | self._silly = value 8 | def _del_silly(self): 9 | print("Whoah, you killed silly!") 10 | del self._silly 11 | 12 | silly = property(_get_silly, _set_silly, _del_silly, 13 | "This is a silly property") 14 | 15 | 16 | -------------------------------------------------------------------------------- /Chapter5/5_09_property_decorator_get.py: -------------------------------------------------------------------------------- 1 | class Foo: 2 | @property 3 | def foo(self): 4 | return "bar" 5 | -------------------------------------------------------------------------------- /Chapter5/5_11_property_decorator_arguments.py: -------------------------------------------------------------------------------- 1 | class Silly: 2 | @property 3 | def silly(self): 4 | "This is a silly property" 5 | print("You are getting silly") 6 | return self._silly 7 | 8 | @silly.setter 9 | def silly(self, value): 10 | print("You are making silly {}".format(value)) 11 | self._silly = value 12 | 13 | @silly.deleter 14 | def silly(self): 15 | print("Whoah, you killed silly!") 16 | del self._silly 17 | -------------------------------------------------------------------------------- /Chapter5/5_12_property_decorator_get_set.py: -------------------------------------------------------------------------------- 1 | class Foo: 2 | @property 3 | def foo(self): 4 | return self._foo 5 | 6 | @foo.setter 7 | def foo(self, value): 8 | self._foo = value 9 | 10 | -------------------------------------------------------------------------------- /Chapter5/5_13_read_only_setattr.py: -------------------------------------------------------------------------------- 1 | class ReadOnlyX: 2 | def __setattr__(self, attr, value): 3 | if attr == "x": 4 | raise AttributeError("X is immutable") 5 | super().__setattr__(attr, value) 6 | -------------------------------------------------------------------------------- /Chapter5/5_14_read_only_getattribute.py: -------------------------------------------------------------------------------- 1 | class ReadOnlyY: 2 | def __getattribute__(self, attr): 3 | if attr == "y": 4 | return "Just Try and Change Me!" 5 | return super().__getattribute__(attr) 6 | -------------------------------------------------------------------------------- /Chapter5/5_15_cache_getter.py: -------------------------------------------------------------------------------- 1 | from urllib.request import urlopen 2 | 3 | class WebPage: 4 | def __init__(self, url): 5 | self.url = url 6 | self._content = None 7 | 8 | @property 9 | def content(self): 10 | if not self._content: 11 | print("Retrieving New Page...") 12 | self._content = urlopen(self.url).read() 13 | return self._content 14 | -------------------------------------------------------------------------------- /Chapter5/5_16_average_property.py: -------------------------------------------------------------------------------- 1 | class AverageList(list): 2 | @property 3 | def average(self): 4 | return sum(self) / len(self) 5 | -------------------------------------------------------------------------------- /Chapter5/5_17_zipsearch.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import shutil 3 | import zipfile 4 | from pathlib import Path 5 | 6 | class ZipReplace: 7 | def __init__(self, filename, search_string, replace_string): 8 | self.filename = filename 9 | self.search_string = search_string 10 | self.replace_string = replace_string 11 | self.temp_directory = Path("unzipped-{}".format( 12 | filename)) 13 | 14 | def zip_find_replace(self): 15 | self.unzip_files() 16 | self.find_replace() 17 | self.zip_files() 18 | 19 | def unzip_files(self): 20 | self.temp_directory.mkdir() 21 | with zipfile.ZipFile(self.filename) as zip: 22 | zip.extractall(str(self.temp_directory)) 23 | 24 | def find_replace(self): 25 | for filename in self.temp_directory.iterdir(): 26 | with filename.open() as file: 27 | contents = file.read() 28 | contents = contents.replace( 29 | self.search_string, self.replace_string) 30 | with filename.open("w") as file: 31 | file.write(contents) 32 | 33 | def zip_files(self): 34 | with zipfile.ZipFile(self.filename, 'w') as file: 35 | for filename in self.temp_directory.iterdir(): 36 | file.write(str(filename), filename.name) 37 | shutil.rmtree(str(self.temp_directory)) 38 | 39 | if __name__ == "__main__": 40 | ZipReplace(*sys.argv[1:4]).zip_find_replace() 41 | -------------------------------------------------------------------------------- /Chapter5/5_18_zipprocessor.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import zipfile 4 | from pathlib import Path 5 | 6 | 7 | class ZipProcessor: 8 | def __init__(self, zipname): 9 | self.zipname = zipname 10 | self.temp_directory = Path("unzipped-{}".format( 11 | zipname[:-4])) 12 | 13 | def process_zip(self): 14 | self.unzip_files() 15 | self.process_files() 16 | self.zip_files() 17 | 18 | def unzip_files(self): 19 | self.temp_directory.mkdir() 20 | with zipfile.ZipFile(self.zipname) as zip: 21 | zip.extractall(str(self.temp_directory)) 22 | 23 | def zip_files(self): 24 | with zipfile.ZipFile(self.zipname, 'w') as file: 25 | for filename in self.temp_directory.iterdir(): 26 | file.write(str(filename), filename.name) 27 | shutil.rmtree(str(self.temp_directory)) 28 | -------------------------------------------------------------------------------- /Chapter5/5_19_zipreplace_inheritance.py: -------------------------------------------------------------------------------- 1 | from zip_processor import ZipProcessor 2 | import sys 3 | import os 4 | 5 | class ZipReplace(ZipProcessor): 6 | def __init__(self, filename, search_string, 7 | replace_string): 8 | super().__init__(filename) 9 | self.search_string = search_string 10 | self.replace_string = replace_string 11 | 12 | def process_files(self): 13 | '''perform a search and replace on all files in the 14 | temporary directory''' 15 | for filename in self.temp_directory.iterdir(): 16 | with filename.open() as file: 17 | contents = file.read() 18 | contents = contents.replace( 19 | self.search_string, self.replace_string) 20 | with filename.open("w") as file: 21 | file.write(contents) 22 | 23 | if __name__ == "__main__": 24 | ZipReplace(*sys.argv[1:4]).process_zip() 25 | -------------------------------------------------------------------------------- /Chapter5/5_20_scaleimage_inheritance.py: -------------------------------------------------------------------------------- 1 | from zip_processor import ZipProcessor 2 | import sys 3 | from PIL import Image 4 | 5 | class ScaleZip(ZipProcessor): 6 | 7 | def process_files(self): 8 | '''Scale each image in the directory to 640x480''' 9 | for filename in self.temp_directory.iterdir(): 10 | im = Image.open(str(filename)) 11 | scaled = im.resize((640, 480)) 12 | scaled.save(str(filename)) 13 | 14 | if __name__ == "__main__": 15 | ScaleZip(*sys.argv[1:4]).process_zip() 16 | -------------------------------------------------------------------------------- /Chapter5/5_24_most_basic_document.py: -------------------------------------------------------------------------------- 1 | class Document: 2 | def __init__(self): 3 | self.characters = [] 4 | self.cursor = 0 5 | self.filename = '' 6 | 7 | def insert(self, character): 8 | self.characters.insert(self.cursor, character) 9 | self.cursor += 1 10 | 11 | def delete(self): 12 | del self.characters[self.cursor] 13 | 14 | def save(self): 15 | with open(self.filename, 'w') as f: 16 | f.write(''.join(self.characters)) 17 | 18 | def forward(self): 19 | self.cursor += 1 20 | 21 | def back(self): 22 | self.cursor -= 1 23 | -------------------------------------------------------------------------------- /Chapter5/5_25_document_cursor.py: -------------------------------------------------------------------------------- 1 | class Cursor: 2 | def __init__(self, document): 3 | self.document = document 4 | self.position = 0 5 | 6 | def forward(self): 7 | self.position += 1 8 | 9 | def back(self): 10 | self.position -= 1 11 | 12 | def home(self): 13 | while self.document.characters[ 14 | self.position-1] != '\n': 15 | self.position -= 1 16 | if self.position == 0: 17 | # Got to beginning of file before newline 18 | break 19 | 20 | def end(self): 21 | while self.position < len(self.document.characters 22 | ) and self.document.characters[self.position] != '\n': 23 | self.position += 1 24 | 25 | 26 | -------------------------------------------------------------------------------- /Chapter5/5_26_document_using_cursor.py: -------------------------------------------------------------------------------- 1 | class Cursor: 2 | def __init__(self, document): 3 | self.document = document 4 | self.position = 0 5 | 6 | def forward(self): 7 | self.position += 1 8 | 9 | def back(self): 10 | self.position -= 1 11 | 12 | def home(self): 13 | while self.document.characters[ 14 | self.position-1] != '\n': 15 | self.position -= 1 16 | if self.position == 0: 17 | # Got to beginning of file before newline 18 | break 19 | 20 | def end(self): 21 | while self.position < len(self.document.characters 22 | ) and self.document.characters[self.position] != '\n': 23 | self.position += 1 24 | 25 | 26 | class Document: 27 | def __init__(self): 28 | self.characters = [] 29 | self.cursor = Cursor(self) 30 | self.filename = '' 31 | 32 | def insert(self, character): 33 | self.characters.insert(self.cursor.position, 34 | character) 35 | self.cursor.forward() 36 | 37 | def delete(self): 38 | del self.characters[self.cursor.position] 39 | 40 | def save(self): 41 | f = open(self.filename, 'w') 42 | f.write(''.join(self.characters)) 43 | f.close() 44 | -------------------------------------------------------------------------------- /Chapter5/5_27_string_property.py: -------------------------------------------------------------------------------- 1 | @property 2 | def string(self): 3 | return "".join(self.characters) 4 | -------------------------------------------------------------------------------- /Chapter5/5_28_Character_class.py: -------------------------------------------------------------------------------- 1 | class Character: 2 | def __init__(self, character, 3 | bold=False, italic=False, underline=False): 4 | assert len(character) == 1 5 | self.character = character 6 | self.bold = bold 7 | self.italic = italic 8 | self.underline = underline 9 | 10 | def __str__(self): 11 | bold = "*" if self.bold else '' 12 | italic = "/" if self.italic else '' 13 | underline = "_" if self.underline else '' 14 | return bold + italic + underline + self.character 15 | -------------------------------------------------------------------------------- /Chapter5/5_29_document_with_character.py: -------------------------------------------------------------------------------- 1 | class Document: 2 | def __init__(self): 3 | self.characters = [] 4 | self.cursor = Cursor(self) 5 | self.filename = '' 6 | 7 | def insert(self, character): 8 | if not hasattr(character, 'character'): 9 | character = Character(character) 10 | self.characters.insert(self.cursor.position, 11 | character) 12 | self.cursor.forward() 13 | 14 | def delete(self): 15 | del self.characters[self.cursor.position] 16 | 17 | def save(self): 18 | f = open(self.filename, 'w') 19 | f.write(''.join(self.characters)) 20 | f.close() 21 | 22 | @property 23 | def string(self): 24 | return "".join((str(c) for c in self.characters)) 25 | 26 | 27 | class Cursor: 28 | def __init__(self, document): 29 | self.document = document 30 | self.position = 0 31 | 32 | def forward(self): 33 | self.position += 1 34 | 35 | def back(self): 36 | self.position -= 1 37 | 38 | def home(self): 39 | while self.document.characters[ 40 | self.position-1].character != '\n': 41 | self.position -= 1 42 | if self.position == 0: 43 | # Got to beginning of file before newline 44 | break 45 | 46 | def end(self): 47 | while self.position < len(self.document.characters) and \ 48 | self.document.characters[ 49 | self.position].character != '\n': 50 | self.position += 1 51 | 52 | class Character: 53 | def __init__(self, character, 54 | bold=False, italic=False, underline=False): 55 | assert len(character) == 1 56 | self.character = character 57 | self.bold = bold 58 | self.italic = italic 59 | self.underline = underline 60 | 61 | def __str__(self): 62 | bold = "*" if self.bold else '' 63 | italic = "/" if self.italic else '' 64 | underline = "_" if self.underline else '' 65 | return bold + italic + underline + self.character 66 | -------------------------------------------------------------------------------- /Chapter5/Document.py: -------------------------------------------------------------------------------- 1 | class Document: 2 | def __init__(self): 3 | self.characters = [] 4 | self.cursor = Cursor(self) 5 | self.filename = '' 6 | 7 | def insert(self, character): 8 | if not hasattr(character, 'character'): 9 | character = Character(character) 10 | self.characters.insert(self.cursor.position, 11 | character) 12 | self.cursor.forward() 13 | 14 | def delete(self): 15 | del self.characters[self.cursor.position] 16 | 17 | def save(self): 18 | f = open(self.filename, 'w') 19 | f.write(''.join(self.characters)) 20 | f.close() 21 | 22 | @property 23 | def string(self): 24 | return "".join((str(c) for c in self.characters)) 25 | 26 | 27 | class Cursor: 28 | def __init__(self, document): 29 | self.document = document 30 | self.position = 0 31 | 32 | def forward(self): 33 | self.position += 1 34 | 35 | def back(self): 36 | self.position -= 1 37 | 38 | def home(self): 39 | while self.document.characters[ 40 | self.position-1].character != '\n': 41 | self.position -= 1 42 | if self.position == 0: 43 | # Got to beginning of file before newline 44 | break 45 | 46 | def end(self): 47 | while self.position < len(self.document.characters) and \ 48 | self.document.characters[ 49 | self.position].character != '\n': 50 | self.position += 1 51 | 52 | class Character: 53 | def __init__(self, character, 54 | bold=False, italic=False, underline=False): 55 | assert len(character) == 1 56 | self.character = character 57 | self.bold = bold 58 | self.italic = italic 59 | self.underline = underline 60 | 61 | def __str__(self): 62 | bold = "*" if self.bold else '' 63 | italic = "/" if self.italic else '' 64 | underline = "_" if self.underline else '' 65 | return bold + italic + underline + self.character 66 | -------------------------------------------------------------------------------- /Chapter5/zip_processor.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import zipfile 4 | from pathlib import Path 5 | 6 | 7 | class ZipProcessor: 8 | def __init__(self, zipname): 9 | self.zipname = zipname 10 | self.temp_directory = Path("unzipped-{}".format( 11 | zipname[:-4])) 12 | 13 | def process_zip(self): 14 | self.unzip_files() 15 | self.process_files() 16 | self.zip_files() 17 | 18 | def unzip_files(self): 19 | self.temp_directory.mkdir() 20 | with zipfile.ZipFile(self.zipname) as zip: 21 | zip.extractall(str(self.temp_directory)) 22 | 23 | def zip_files(self): 24 | with zipfile.ZipFile(self.zipname, 'w') as file: 25 | for filename in self.temp_directory.iterdir(): 26 | file.write(str(filename), filename.name) 27 | shutil.rmtree(str(self.temp_directory)) 28 | -------------------------------------------------------------------------------- /Chapter5/zip_processor.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter5/zip_processor.pyc -------------------------------------------------------------------------------- /Chapter6/6_01_empty_object.py: -------------------------------------------------------------------------------- 1 | class MyObject: 2 | pass 3 | -------------------------------------------------------------------------------- /Chapter6/6_02_pass_tuple_to_function.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | def middle(stock, date): 3 | symbol, current, high, low = stock 4 | return (((high + low) /2), date) 5 | 6 | mid_value, date = middle(("GOOG", 613.30, 625.86, 610.50), 7 | datetime.date(2010, 1, 6)) 8 | -------------------------------------------------------------------------------- /Chapter6/6_03_named_tuple.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | Stock = namedtuple("Stock", "symbol current high low") 3 | stock = Stock("FB", 75.00, high=75.03, low=74.90) 4 | -------------------------------------------------------------------------------- /Chapter6/6_04_dict_stocks.py: -------------------------------------------------------------------------------- 1 | stocks = {"GOOG": (613.30, 625.86, 610.50), 2 | "MSFT": (30.25, 30.70, 30.19)} 3 | -------------------------------------------------------------------------------- /Chapter6/6_05_random_key_dict.py: -------------------------------------------------------------------------------- 1 | random_keys = {} 2 | random_keys["astring"] = "somestring" 3 | random_keys[5] = "aninteger" 4 | random_keys[25.2] = "floats work too" 5 | random_keys[("abc", 123)] = "so do tuples" 6 | 7 | 8 | class AnObject: 9 | def __init__(self, avalue): 10 | self.avalue = avalue 11 | 12 | my_object = AnObject(14) 13 | random_keys[my_object] = "We can even store objects" 14 | my_object.avalue = 12 15 | try: 16 | random_keys[[1,2,3]] = "we can't store lists though" 17 | except: 18 | print("unable to store list\n") 19 | 20 | for key, value in random_keys.items(): 21 | print("{} has value {}".format(key, value)) 22 | -------------------------------------------------------------------------------- /Chapter6/6_06_setdefault_frequency.py: -------------------------------------------------------------------------------- 1 | def letter_frequency(sentence): 2 | frequencies = {} 3 | for letter in sentence: 4 | frequency = frequencies.setdefault(letter, 0) 5 | frequencies[letter] = frequency + 1 6 | return frequencies 7 | -------------------------------------------------------------------------------- /Chapter6/6_07_defaultdict_frequency.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | def letter_frequency(sentence): 3 | frequencies = defaultdict(int) 4 | for letter in sentence: 5 | frequencies[letter] += 1 6 | return frequencies 7 | -------------------------------------------------------------------------------- /Chapter6/6_08_defaultdict_custom_function.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | num_items = 0 3 | def tuple_counter(): 4 | global num_items 5 | num_items += 1 6 | return (num_items, []) 7 | 8 | d = defaultdict(tuple_counter) 9 | -------------------------------------------------------------------------------- /Chapter6/6_09_counter_frequency.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | def letter_frequency(sentence): 3 | return Counter(sentence) 4 | -------------------------------------------------------------------------------- /Chapter6/6_09_counter_poll.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | 3 | responses = [ 4 | "vanilla", 5 | "chocolate", 6 | "vanilla", 7 | "vanilla", 8 | "caramel", 9 | "strawberry", 10 | "vanilla" 11 | ] 12 | 13 | print( 14 | "The children voted for {} ice cream".format( 15 | Counter(responses).most_common(1)[0][0] 16 | ) 17 | ) 18 | -------------------------------------------------------------------------------- /Chapter6/6_09_list_tuple_frequency.py: -------------------------------------------------------------------------------- 1 | import string 2 | CHARACTERS = list(string.ascii_letters) + [" "] 3 | 4 | def letter_frequency(sentence): 5 | frequencies = [(c, 0) for c in CHARACTERS] 6 | for letter in sentence: 7 | index = CHARACTERS.index(letter) 8 | frequencies[index] = (letter,frequencies[index][1] + 1) 9 | return frequencies 10 | -------------------------------------------------------------------------------- /Chapter6/6_10_object_comparison.py: -------------------------------------------------------------------------------- 1 | from functools import total_ordering 2 | 3 | @total_ordering 4 | class WeirdSortee: 5 | def __init__(self, string, number, sort_num): 6 | self.string = string 7 | self.number = number 8 | self.sort_num = sort_num 9 | 10 | def __lt__(self, object): 11 | if self.sort_num: 12 | return self.number < object.number 13 | return self.string < object.string 14 | 15 | def __repr__(self): 16 | return"{}:{}".format(self.string, self.number) 17 | 18 | def __eq__(self, object): 19 | return all(( 20 | self.string == object.string, 21 | self.number == object.number, 22 | self.sort_num == object.number 23 | )) 24 | -------------------------------------------------------------------------------- /Chapter6/6_11_song_artist_set.py: -------------------------------------------------------------------------------- 1 | song_library = [("Phantom Of The Opera", "Sarah Brightman"), 2 | ("Knocking On Heaven's Door", "Guns N' Roses"), 3 | ("Captain Nemo", "Sarah Brightman"), 4 | ("Patterns In The Ivy", "Opeth"), 5 | ("November Rain", "Guns N' Roses"), 6 | ("Beautiful", "Sarah Brightman"), 7 | ("Mal's Song", "Vixy and Tony")] 8 | 9 | artists = set() 10 | for song, artist in song_library: 11 | artists.add(artist) 12 | 13 | print(artists) 14 | -------------------------------------------------------------------------------- /Chapter6/6_12_set_operations.py: -------------------------------------------------------------------------------- 1 | my_artists = {"Sarah Brightman", "Guns N' Roses", 2 | "Opeth", "Vixy and Tony"} 3 | 4 | auburns_artists = {"Nickelback", "Guns N' Roses", 5 | "Savage Garden"} 6 | 7 | print("All: {}".format(my_artists.union(auburns_artists))) 8 | print("Both: {}".format(auburns_artists.intersection(my_artists))) 9 | print("Either but not both: {}".format( 10 | my_artists.symmetric_difference(auburns_artists))) 11 | -------------------------------------------------------------------------------- /Chapter6/6_13_set_operations2.py: -------------------------------------------------------------------------------- 1 | my_artists = {"Sarah Brightman", "Guns N' Roses", 2 | "Opeth", "Vixy and Tony"} 3 | 4 | bands = {"Guns N' Roses", "Opeth"} 5 | 6 | print("my_artists is to bands:") 7 | print("issuperset: {}".format(my_artists.issuperset(bands))) 8 | print("issubset: {}".format(my_artists.issubset(bands))) 9 | print("difference: {}".format(my_artists.difference(bands))) 10 | print("*"*20) 11 | print("bands is to my_artists:") 12 | print("issuperset: {}".format(bands.issuperset(my_artists))) 13 | print("issubset: {}".format(bands.issubset(my_artists))) 14 | print("difference: {}".format(bands.difference(my_artists))) 15 | -------------------------------------------------------------------------------- /Chapter6/6_14_oop_pairs.py: -------------------------------------------------------------------------------- 1 | c = a + b 2 | c = a.add(b) 3 | 4 | l[0] = 5 5 | l.setitem(0, 5) 6 | 7 | d[key] = value 8 | d.setitem(key, value) 9 | 10 | for x in alist: 11 | #do something with x 12 | it = alist.iterator() 13 | while it.has_next(): 14 | x = it.next() 15 | #do something with x 16 | 17 | -------------------------------------------------------------------------------- /Chapter6/6_15_stupid_adding_integer.py: -------------------------------------------------------------------------------- 1 | class SillyInt(int): 2 | def __add__(self, num): 3 | return 0 4 | -------------------------------------------------------------------------------- /Chapter6/6_16_dictsorted.py: -------------------------------------------------------------------------------- 1 | from collections import KeysView, ItemsView, ValuesView 2 | class DictSorted(dict): 3 | def __new__(*args, **kwargs): 4 | new_dict = dict.__new__(*args, **kwargs) 5 | new_dict.ordered_keys = [] 6 | return new_dict 7 | 8 | def __setitem__(self, key, value): 9 | '''self[key] = value syntax''' 10 | if key not in self.ordered_keys: 11 | self.ordered_keys.append(key) 12 | super().__setitem__(key, value) 13 | 14 | def setdefault(self, key, value): 15 | if key not in self.ordered_keys: 16 | self.ordered_keys.append(key) 17 | return super().setdefault(key, value) 18 | 19 | def keys(self): 20 | return KeysView(self) 21 | 22 | def values(self): 23 | return ValuesView(self) 24 | 25 | def items(self): 26 | return ItemsView(self) 27 | 28 | def __iter__(self): 29 | '''for x in self syntax''' 30 | return self.ordered_keys.__iter__() 31 | -------------------------------------------------------------------------------- /Chapter6/6_17_link_parser.py: -------------------------------------------------------------------------------- 1 | from urllib.request import urlopen 2 | from urllib.parse import urlparse 3 | import re 4 | import sys 5 | LINK_REGEX = re.compile( 6 | "]*href=['\"]([^'\"]+)['\"][^>]*>") 7 | 8 | class LinkCollector: 9 | def __init__(self, url): 10 | self.url = "http://" + urlparse(url).netloc 11 | 12 | def collect_links(self, path="/"): 13 | full_url = self.url + path 14 | page = str(urlopen(full_url).read()) 15 | links = LINK_REGEX.findall(page) 16 | print(links) 17 | 18 | if __name__ == "__main__": 19 | LinkCollector(sys.argv[1]).collect_links() 20 | -------------------------------------------------------------------------------- /Chapter6/6_18_normalize_url.py: -------------------------------------------------------------------------------- 1 | def normalize_url(self, path, link): 2 | if link.startswith("http://"): 3 | return link 4 | elif link.startswith("/"): 5 | return self.url + link 6 | else: 7 | return self.url + path.rpartition('/' 8 | )[0] + '/' + link 9 | -------------------------------------------------------------------------------- /Chapter6/6_19_visited_links_sets.py: -------------------------------------------------------------------------------- 1 | class LinkCollector: 2 | def __init__(self, url): 3 | self.url = "http://" + urlparse(url).netloc 4 | self.collected_links = set() 5 | self.visited_links = set() 6 | 7 | def collect_links(self, path="/"): 8 | full_url = self.url + path 9 | self.visited_links.add(full_url) 10 | page = str(urlopen(full_url).read()) 11 | links = LINK_REGEX.findall(page) 12 | links = {self.normalize_url(path, link 13 | ) for link in links} 14 | self.collected_links = links.union( 15 | self.collected_links) 16 | unvisited_links = links.difference( 17 | self.visited_links) 18 | print(links, self.visited_links, 19 | self.collected_links, unvisited_links) 20 | 21 | def normalize_url(self, path, link): 22 | if link.startswith("http://"): 23 | return link 24 | elif link.startswith("/"): 25 | return self.url + link 26 | else: 27 | return self.url + path.rpartition('/' 28 | )[0] + '/' + link 29 | -------------------------------------------------------------------------------- /Chapter6/6_20_collect_remaining_links.py: -------------------------------------------------------------------------------- 1 | for link in unvisited_links: 2 | if not link.startswith(self.url): 3 | continue 4 | self.collect_links(urlparse(link).path) 5 | -------------------------------------------------------------------------------- /Chapter6/6_21_print_collected_links.py: -------------------------------------------------------------------------------- 1 | if __name__ == "__main__": 2 | collector = LinkCollector(sys.argv[1]) 3 | collector.collect_links() 4 | for link in collector.collected_links: 5 | print(link) 6 | -------------------------------------------------------------------------------- /Chapter6/6_22_dict_link_collector.py: -------------------------------------------------------------------------------- 1 | from urllib.request import urlopen 2 | from urllib.parse import urlparse 3 | import re 4 | import sys 5 | LINK_REGEX = re.compile( 6 | "]*href=['\"]([^'\"]+)['\"][^>]*>") 7 | 8 | class LinkCollector: 9 | def __init__(self, url): 10 | self.url = "http://%s" % urlparse(url).netloc 11 | self.collected_links = {} 12 | self.visited_links = set() 13 | 14 | def collect_links(self, path="/"): 15 | full_url = self.url + path 16 | self.visited_links.add(full_url) 17 | page = str(urlopen(full_url).read()) 18 | links = LINK_REGEX.findall(page) 19 | links = {self.normalize_url(path, link 20 | ) for link in links} 21 | self.collected_links[full_url] = links 22 | for link in links: 23 | self.collected_links.setdefault(link, set()) 24 | unvisited_links = links.difference( 25 | self.visited_links) 26 | for link in unvisited_links: 27 | if link.startswith(self.url): 28 | self.collect_links(urlparse(link).path) 29 | 30 | def normalize_url(self, path, link): 31 | if link.startswith("http://"): 32 | return link 33 | elif link.startswith("/"): 34 | return self.url + link 35 | else: 36 | return self.url + path.rpartition('/' 37 | )[0] + '/' + link 38 | 39 | if __name__ == "__main__": 40 | collector = LinkCollector(sys.argv[1]) 41 | collector.collect_links() 42 | for link, item in collector.collected_links.items(): 43 | print("{}: {}".format(link, item)) 44 | -------------------------------------------------------------------------------- /Chapter6/6_23_queue_link_collector.py: -------------------------------------------------------------------------------- 1 | from urllib.request import urlopen 2 | from urllib.parse import urlparse 3 | import re 4 | import sys 5 | from queue import Queue 6 | LINK_REGEX = re.compile("]*href=['\"]([^'\"]+)['\"][^>]*>") 7 | 8 | 9 | class LinkCollector: 10 | def __init__(self, url): 11 | self.url = "http://%s" % urlparse(url).netloc 12 | self.collected_links = {} 13 | self.visited_links = set() 14 | 15 | def collect_links(self): 16 | queue = Queue() 17 | queue.put(self.url) 18 | while not queue.empty(): 19 | url = queue.get().rstrip('/') 20 | self.visited_links.add(url) 21 | page = str(urlopen(url).read()) 22 | links = LINK_REGEX.findall(page) 23 | links = { 24 | self.normalize_url(urlparse(url).path, link) 25 | for link in links 26 | } 27 | self.collected_links[url] = links 28 | for link in links: 29 | self.collected_links.setdefault(link, set()) 30 | unvisited_links = links.difference(self.visited_links) 31 | for link in unvisited_links: 32 | if link.startswith(self.url): 33 | queue.put(link) 34 | 35 | def normalize_url(self, path, link): 36 | if link.startswith("http://"): 37 | return link.rstrip('/') 38 | elif link.startswith("/"): 39 | return self.url + link.rstrip('/') 40 | else: 41 | return self.url + path.rpartition('/')[0] + '/' + link.rstrip('/') 42 | 43 | if __name__ == "__main__": 44 | collector = LinkCollector(sys.argv[1]) 45 | collector.collect_links() 46 | for link, item in collector.collected_links.items(): 47 | print("%s: %s" % (link, item)) 48 | -------------------------------------------------------------------------------- /Chapter6/case_study_serve/blog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | My real blog is here if you are 4 | interested. 5 | 6 | Here are some links: 7 | The first page 8 | Some contact info 9 | All about my dog! 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter6/case_study_serve/contact.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | I can be contacted in lots of ways. Here are some links: 4 | 5 | My Blog 6 | Home 7 | Contact 8 | Dad's books 9 | 10 | 11 | -------------------------------------------------------------------------------- /Chapter6/case_study_serve/esme.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | I have a Cavalier King Charles Spaniel named Esme. She's named after Esme (for 4 | Esmerelda) Weatherwax, from Terry Pratchett's Discworld series. 5 | 6 | Here's some links: 7 | 8 | My hobbies 9 | Wikipedia 10 | on Cavaliers 11 | 12 | 13 | -------------------------------------------------------------------------------- /Chapter6/case_study_serve/hobbies.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | I do yoga, woodworking, skating, writing, and programming. 4 | 5 | My studio 6 | Yoga stuff 7 | The home page 8 | Esme is my hobby too 9 | Recent stuff 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter6/case_study_serve/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Contact us 4 | Blog 5 | My Dog 6 | Some hobbies 7 | Contact AGAIN 8 | Favourite OS 9 | 10 | 11 | -------------------------------------------------------------------------------- /Chapter6/case_study_serve/yoga.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | I have studied Kung Fu, Tai Chi, Chi Kung, fan, and karate. 4 | Now I practice yoga at Be Luminous. 5 | 6 | 7 | -------------------------------------------------------------------------------- /Chapter6/link_collector.py: -------------------------------------------------------------------------------- 1 | from urllib.request import urlopen 2 | from urllib.parse import urlparse 3 | import re 4 | import sys 5 | from queue import Queue 6 | LINK_REGEX = re.compile("]*href=['\"]([^'\"]+)['\"][^>]*>") 7 | 8 | 9 | class LinkCollector: 10 | def __init__(self, url): 11 | self.url = "http://%s" % urlparse(url).netloc 12 | self.collected_links = {} 13 | self.visited_links = set() 14 | 15 | def collect_links(self): 16 | queue = Queue() 17 | queue.put(self.url) 18 | while not queue.empty(): 19 | url = queue.get().rstrip('/') 20 | self.visited_links.add(url) 21 | page = str(urlopen(url).read()) 22 | links = LINK_REGEX.findall(page) 23 | links = { 24 | self.normalize_url(urlparse(url).path, link) 25 | for link in links 26 | } 27 | self.collected_links[url] = links 28 | for link in links: 29 | self.collected_links.setdefault(link, set()) 30 | unvisited_links = links.difference(self.visited_links) 31 | for link in unvisited_links: 32 | if link.startswith(self.url): 33 | queue.put(link) 34 | 35 | def normalize_url(self, path, link): 36 | if link.startswith("http://"): 37 | return link.rstrip('/') 38 | elif link.startswith("/"): 39 | return self.url + link.rstrip('/') 40 | else: 41 | return self.url + path.rpartition('/')[0] + '/' + link.rstrip('/') 42 | 43 | if __name__ == "__main__": 44 | collector = LinkCollector(sys.argv[1]) 45 | collector.collect_links() 46 | for link, item in collector.collected_links.items(): 47 | print("%s: %s" % (link, item)) 48 | -------------------------------------------------------------------------------- /Chapter7/7_01_reversible_objects.py: -------------------------------------------------------------------------------- 1 | normal_list=[1,2,3,4,5] 2 | 3 | class CustomSequence(): 4 | def __len__(self): 5 | return 5 6 | 7 | def __getitem__(self, index): 8 | return "x{0}".format(index) 9 | 10 | class FunkyBackwards(): 11 | def __reversed__(self): 12 | return "BACKWARDS!" 13 | 14 | for seq in normal_list, CustomSequence(), FunkyBackwards(): 15 | print("\n{}: ".format(seq.__class__.__name__), end="") 16 | for item in reversed(seq): 17 | print(item, end=", ") 18 | -------------------------------------------------------------------------------- /Chapter7/7_02_enumerate_line_numbers.py: -------------------------------------------------------------------------------- 1 | import sys 2 | filename = sys.argv[1] 3 | 4 | with open(filename) as file: 5 | for index, line in enumerate(file): 6 | print("{0}: {1}".format(index+1, line), end='') 7 | -------------------------------------------------------------------------------- /Chapter7/7_03.1_zip_to_enumerate.py: -------------------------------------------------------------------------------- 1 | 2 | def zip_enumerate(container): 3 | return zip(range(len(container)), container) 4 | 5 | for idx, val in zip_enumerate("hello world"): 6 | print(idx, val) 7 | -------------------------------------------------------------------------------- /Chapter7/7_03_enumerate_max_min.py: -------------------------------------------------------------------------------- 1 | from operator import itemgetter 2 | 3 | def min_max_indexes(seq): 4 | minimum = min(enumerate(seq), key=itemgetter(1)) 5 | maximum = max(enumerate(seq), key=itemgetter(1)) 6 | return minimum[0], maximum[0] 7 | -------------------------------------------------------------------------------- /Chapter7/7_04_tdf_contact.txt: -------------------------------------------------------------------------------- 1 | first last email 2 | john smith jsmith@example.com 3 | jane doan janed@example.com 4 | david neilson dn@example.com 5 | -------------------------------------------------------------------------------- /Chapter7/7_05_read_file.py: -------------------------------------------------------------------------------- 1 | file = open('filename') 2 | print(file.read()) 3 | -------------------------------------------------------------------------------- /Chapter7/7_05_tdf_processor.py: -------------------------------------------------------------------------------- 1 | import sys 2 | filename = sys.argv[1] 3 | 4 | with open(filename) as file: 5 | header = file.readline().strip().split('\t') 6 | contacts = [ 7 | dict( 8 | zip(header, line.strip().split('\t')) 9 | ) for line in file 10 | ] 11 | 12 | for contact in contacts: 13 | print("email: {email} -- {last}, {first}".format( 14 | **contact)) 15 | -------------------------------------------------------------------------------- /Chapter7/7_06_write.py: -------------------------------------------------------------------------------- 1 | contents = "Some file contents" 2 | file = open("filename", "w") 3 | file.write(contents) 4 | file.close() 5 | -------------------------------------------------------------------------------- /Chapter7/7_07_with.py: -------------------------------------------------------------------------------- 1 | with open('filename') as file: 2 | for line in file: 3 | print(line, end='') 4 | -------------------------------------------------------------------------------- /Chapter7/7_08_context_manager.py: -------------------------------------------------------------------------------- 1 | class StringJoiner(list): 2 | def __enter__(self): 3 | return self 4 | 5 | def __exit__(self, type, value, tb): 6 | self.result = "".join(self) 7 | 8 | import random, string 9 | with StringJoiner() as joiner: 10 | for i in range(15): 11 | joiner.append(random.choice(string.ascii_letters)) 12 | 13 | print(joiner.result) 14 | -------------------------------------------------------------------------------- /Chapter7/7_18_bad_kw_default.py: -------------------------------------------------------------------------------- 1 | number = 5 2 | def funky_function(number=number): 3 | print(number) 4 | 5 | number=6 6 | funky_function(8) 7 | funky_function() 8 | print(number) 9 | -------------------------------------------------------------------------------- /Chapter7/7_19_link_downloader.py: -------------------------------------------------------------------------------- 1 | from collections import Iterable 2 | 3 | def get_pages(links): 4 | if not isinstance(links, Iterable) or isinstance( 5 | links, (bytes, str)): 6 | links = [links] 7 | for link in links: 8 | #download the link with urllib 9 | print(link) 10 | 11 | -------------------------------------------------------------------------------- /Chapter7/7_20_link_downloader_vararg.py: -------------------------------------------------------------------------------- 1 | def get_pages(*links): 2 | for link in links: 3 | #download the link with urllib 4 | print(link) 5 | 6 | -------------------------------------------------------------------------------- /Chapter7/7_21_kwarg_options.py: -------------------------------------------------------------------------------- 1 | class Options: 2 | default_options = { 3 | 'port': 21, 4 | 'host': 'localhost', 5 | 'username': None, 6 | 'password': None, 7 | 'debug': False, 8 | } 9 | def __init__(self, **kwargs): 10 | self.options = dict(Options.default_options) 11 | self.options.update(kwargs) 12 | 13 | def __getitem__(self, key): 14 | return self.options[key] 15 | -------------------------------------------------------------------------------- /Chapter7/7_22_all_arguments.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import os.path 3 | def augmented_move(target_folder, *filenames, 4 | verbose=False, **specific): 5 | '''Move all filenames into the target_folder, allowing 6 | specific treatment of certain files.''' 7 | 8 | def print_verbose(message, filename): 9 | '''print the message only if verbose is enabled''' 10 | if verbose: 11 | print(message.format(filename)) 12 | 13 | for filename in filenames: 14 | target_path = os.path.join(target_folder, filename) 15 | if filename in specific: 16 | if specific[filename] == 'ignore': 17 | print_verbose("Ignoring {0}", filename) 18 | elif specific[filename] == 'copy': 19 | print_verbose("Copying {0}", filename) 20 | shutil.copyfile(filename, target_path) 21 | else: 22 | print_verbose("Moving {0}", filename) 23 | shutil.move(filename, target_path) 24 | -------------------------------------------------------------------------------- /Chapter7/7_23_unpacking_arguments.py: -------------------------------------------------------------------------------- 1 | def show_args(arg1, arg2, arg3="THREE"): 2 | print(arg1, arg2, arg3) 3 | 4 | some_args = range(3) 5 | more_args = { 6 | "arg1": "ONE", 7 | "arg2": "TWO"} 8 | 9 | print("Unpacking a sequence:", end=" ") 10 | show_args(*some_args) 11 | print("Unpacking a dict:", end=" ") 12 | show_args(**more_args) 13 | -------------------------------------------------------------------------------- /Chapter7/7_24_function_object.py: -------------------------------------------------------------------------------- 1 | def my_function(): 2 | print("The Function Was Called") 3 | my_function.description = "A silly function" 4 | 5 | def second_function(): 6 | print("The second was called") 7 | second_function.description = "A sillier function." 8 | 9 | def another_function(function): 10 | print("The description:", end=" ") 11 | print(function.description) 12 | print("The name:", end=" ") 13 | print(function.__name__) 14 | print("The class:", end=" ") 15 | print(function.__class__) 16 | print("Now I'll call the function passed in") 17 | function() 18 | 19 | another_function(my_function) 20 | another_function(second_function) 21 | -------------------------------------------------------------------------------- /Chapter7/7_25_timer.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import time 3 | 4 | class TimedEvent: 5 | def __init__(self, endtime, callback): 6 | self.endtime = endtime 7 | self.callback = callback 8 | 9 | def ready(self): 10 | return self.endtime <= datetime.datetime.now() 11 | 12 | class Timer: 13 | def __init__(self): 14 | self.events = [] 15 | 16 | def call_after(self, delay, callback): 17 | end_time = datetime.datetime.now() + \ 18 | datetime.timedelta(seconds=delay) 19 | 20 | self.events.append(TimedEvent(end_time, callback)) 21 | 22 | def run(self): 23 | while True: 24 | ready_events = (e for e in self.events if e.ready()) 25 | for event in ready_events: 26 | event.callback(self) 27 | self.events.remove(event) 28 | time.sleep(0.5) 29 | -------------------------------------------------------------------------------- /Chapter7/7_26_timer_test.py: -------------------------------------------------------------------------------- 1 | from timer import Timer 2 | import datetime 3 | 4 | def format_time(message, *args): 5 | now = datetime.datetime.now().strftime("%I:%M:%S") 6 | print(message.format(*args, now=now)) 7 | 8 | def one(timer): 9 | format_time("{now}: Called One") 10 | 11 | def two(timer): 12 | format_time("{now}: Called Two") 13 | 14 | def three(timer): 15 | format_time("{now}: Called Three") 16 | 17 | class Repeater: 18 | def __init__(self): 19 | self.count = 0 20 | def repeater(self, timer): 21 | format_time("{now}: repeat {0}", self.count) 22 | self.count += 1 23 | timer.call_after(5, self.repeater) 24 | 25 | timer = Timer() 26 | timer.call_after(1, one) 27 | timer.call_after(2, one) 28 | timer.call_after(2, two) 29 | timer.call_after(4, two) 30 | timer.call_after(3, three) 31 | timer.call_after(6, three) 32 | repeater = Repeater() 33 | timer.call_after(5, repeater.repeater) 34 | format_time("{now}: Starting") 35 | timer.run() 36 | -------------------------------------------------------------------------------- /Chapter7/7_27_add_function_to_object.py: -------------------------------------------------------------------------------- 1 | class A: 2 | def print(self): 3 | print("my class is A") 4 | 5 | def fake_print(): 6 | print("my class is not A") 7 | 8 | a = A() 9 | a.print() 10 | a.print = fake_print 11 | a.print() 12 | -------------------------------------------------------------------------------- /Chapter7/7_28_callable_repeat.py: -------------------------------------------------------------------------------- 1 | from timer import Timer 2 | import datetime 3 | 4 | def format_time(message, *args): 5 | now = datetime.datetime.now().strftime("%I:%M:%S") 6 | print(message.format(*args, now=now)) 7 | 8 | class Repeater: 9 | def __init__(self): 10 | self.count = 0 11 | def __call__(self, timer): 12 | format_time("{now}: repeat {0}", self.count) 13 | self.count += 1 14 | timer.call_after(5, self) 15 | 16 | timer = Timer() 17 | timer.call_after(5, Repeater()) 18 | format_time("{now}: Starting") 19 | timer.run() 20 | -------------------------------------------------------------------------------- /Chapter7/7_29_send_email.py: -------------------------------------------------------------------------------- 1 | import smtplib 2 | from email.mime.text import MIMEText 3 | 4 | def send_email(subject, message, from_addr, *to_addrs, 5 | host="localhost", port=1025, **headers): 6 | 7 | email = MIMEText(message) 8 | email['Subject'] = subject 9 | email['From'] = from_addr 10 | for header, value in headers.items(): 11 | email[header] = value 12 | 13 | sender = smtplib.SMTP(host, port) 14 | for addr in to_addrs: 15 | del email['To'] 16 | email['To'] = addr 17 | sender.sendmail(from_addr, addr, email.as_string()) 18 | sender.quit() 19 | -------------------------------------------------------------------------------- /Chapter7/7_30_send_email_dict_headers.py: -------------------------------------------------------------------------------- 1 | import smtplib 2 | from email.mime.text import MIMEText 3 | 4 | def send_email(subject, message, from_addr, *to_addrs, 5 | host="localhost", port=1025, headers=None): 6 | 7 | headers = {} if headers is None else headers 8 | 9 | email = MIMEText(message) 10 | email['Subject'] = subject 11 | email['From'] = from_addr 12 | for header, value in headers.items(): 13 | email[header] = value 14 | 15 | sender = smtplib.SMTP(host, port) 16 | for addr in to_addrs: 17 | del email['To'] 18 | email['To'] = addr 19 | sender.sendmail(from_addr, addr, email.as_string()) 20 | sender.quit() 21 | -------------------------------------------------------------------------------- /Chapter7/7_32_mailing_list_defaultdict_set.py: -------------------------------------------------------------------------------- 1 | 2 | class MailingList: 3 | '''Manage groups of e-mail addresses for sending e-mails.''' 4 | 5 | def __init__(self): 6 | self.email_map = defaultdict(set) 7 | 8 | def add_to_group(self, email, group): 9 | self.email_map[email].add(group) 10 | -------------------------------------------------------------------------------- /Chapter7/7_33_mailing_list_get_emails.py: -------------------------------------------------------------------------------- 1 | 2 | def emails_in_groups(self, *groups): 3 | groups = set(groups) 4 | emails = set() 5 | for e, g in self.email_map.items(): 6 | if g & groups: 7 | emails.add(e) 8 | return emails 9 | -------------------------------------------------------------------------------- /Chapter7/7_34_send_mailing.py: -------------------------------------------------------------------------------- 1 | 2 | def send_mailing(self, subject, message, from_addr, 3 | *groups, headers=None): 4 | emails = self.emails_in_groups(*groups) 5 | send_email(subject, message, from_addr, 6 | *emails, headers=headers) 7 | -------------------------------------------------------------------------------- /Chapter7/7_35_load_save.py: -------------------------------------------------------------------------------- 1 | def save(self): 2 | with open(self.data_file, 'w') as file: 3 | for email, groups in self.email_map.items(): 4 | file.write( 5 | '{} {}\n'.format(email, ','.join(groups)) 6 | ) 7 | 8 | def load(self): 9 | self.email_map = defaultdict(set) 10 | try: 11 | with open(self.data_file) as file: 12 | for line in file: 13 | email, groups = line.strip().split(' ') 14 | groups = set(groups.split(',')) 15 | self.email_map[email] = groups 16 | except IOError: 17 | pass 18 | -------------------------------------------------------------------------------- /Chapter7/7_36_enter_exit.py: -------------------------------------------------------------------------------- 1 | def __enter__(self): 2 | self.load() 3 | return self 4 | 5 | def __exit__(self, type, value, tb): 6 | self.save() 7 | -------------------------------------------------------------------------------- /Chapter7/__pycache__/timer.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter7/__pycache__/timer.cpython-34.pyc -------------------------------------------------------------------------------- /Chapter7/addresses.db: -------------------------------------------------------------------------------- 1 | friend1@example.com friends 2 | friend2@example.com friends 3 | family1@example.com family,friends 4 | -------------------------------------------------------------------------------- /Chapter7/mailing_list.py: -------------------------------------------------------------------------------- 1 | import smtplib 2 | from email.mime.text import MIMEText 3 | from collections import defaultdict 4 | 5 | 6 | def send_email(subject, message, from_addr, *to_addrs, 7 | host="localhost", port=1025, headers=None): 8 | 9 | headers = {} if headers is None else headers 10 | 11 | email = MIMEText(message) 12 | email['Subject'] = subject 13 | email['From'] = from_addr 14 | for header, value in headers.items(): 15 | email[header] = value 16 | 17 | sender = smtplib.SMTP(host, port) 18 | for addr in to_addrs: 19 | del email['To'] 20 | email['To'] = addr 21 | sender.sendmail(from_addr, addr, email.as_string()) 22 | sender.quit() 23 | 24 | 25 | class MailingList: 26 | '''Manage groups of e-mail addresses for sending e-mails.''' 27 | 28 | def __init__(self, data_file): 29 | self.data_file = data_file 30 | self.email_map = defaultdict(set) 31 | 32 | def add_to_group(self, email, group): 33 | self.email_map[email].add(group) 34 | 35 | def emails_in_groups(self, *groups): 36 | groups = set(groups) 37 | emails = set() 38 | for e, g in self.email_map.items(): 39 | if g & groups: 40 | emails.add(e) 41 | return emails 42 | 43 | def send_mailing(self, subject, message, from_addr, 44 | *groups, headers=None): 45 | emails = self.emails_in_groups(*groups) 46 | send_email(subject, message, from_addr, 47 | *emails, headers=headers) 48 | 49 | def save(self): 50 | with open(self.data_file, 'w') as file: 51 | for email, groups in self.email_map.items(): 52 | file.write( 53 | '{} {}\n'.format(email, ','.join(groups)) 54 | ) 55 | 56 | def load(self): 57 | self.email_map = defaultdict(set) 58 | try: 59 | with open(self.data_file) as file: 60 | for line in file: 61 | email, groups = line.strip().split(' ') 62 | groups = set(groups.split(',')) 63 | self.email_map[email] = groups 64 | except IOError: 65 | pass 66 | 67 | def __enter__(self): 68 | self.load() 69 | return self 70 | 71 | def __exit__(self, type, value, tb): 72 | self.save() 73 | 74 | -------------------------------------------------------------------------------- /Chapter7/timer.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import time 3 | 4 | class TimedEvent: 5 | def __init__(self, endtime, callback): 6 | self.endtime = endtime 7 | self.callback = callback 8 | 9 | def ready(self): 10 | return self.endtime <= datetime.datetime.now() 11 | 12 | class Timer: 13 | def __init__(self): 14 | self.events = [] 15 | 16 | def call_after(self, delay, callback): 17 | end_time = datetime.datetime.now() + \ 18 | datetime.timedelta(seconds=delay) 19 | 20 | self.events.append(TimedEvent(end_time, callback)) 21 | 22 | def run(self): 23 | while True: 24 | ready_events = (e for e in self.events if e.ready()) 25 | for event in ready_events: 26 | event.callback(self) 27 | self.events.remove(event) 28 | time.sleep(0.5) 29 | -------------------------------------------------------------------------------- /Chapter7/timer.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/5add8c66ccef17baad97140fcdb142bfa5337085/Chapter7/timer.pyc -------------------------------------------------------------------------------- /Chapter8/8_01_string_creation.py: -------------------------------------------------------------------------------- 1 | a = "hello" 2 | b = 'world' 3 | c = '''a multiple 4 | line string''' 5 | d = """More 6 | multiple""" 7 | e = ("Three " "Strings " 8 | "Together") 9 | -------------------------------------------------------------------------------- /Chapter8/8_02_format_empty.py: -------------------------------------------------------------------------------- 1 | template = "Hello {}, you are currently {}." 2 | print(template.format('Dusty', 'writing')) 3 | -------------------------------------------------------------------------------- /Chapter8/8_03_format_position.py: -------------------------------------------------------------------------------- 1 | template = "Hello {0}, you are {1}. Your name is {0}." 2 | print(template.format('Dusty', 'writing')) 3 | -------------------------------------------------------------------------------- /Chapter8/8_04_format_some_positions_broken.py: -------------------------------------------------------------------------------- 1 | template = "Hello {}, you are {}. Your name is {0}." 2 | print(template.format('Dusty', 'writing')) 3 | -------------------------------------------------------------------------------- /Chapter8/8_05_brace_escape.py: -------------------------------------------------------------------------------- 1 | template = """ 2 | public class {0} {{ 3 | public static void main(String[] args) {{ 4 | System.out.println("{1}"); 5 | }} 6 | }}""" 7 | 8 | print(template.format("MyClass", "print('hello world')")); 9 | -------------------------------------------------------------------------------- /Chapter8/8_06_format_kw_args.py: -------------------------------------------------------------------------------- 1 | template = """ 2 | From: <{from_email}> 3 | To: <{to_email}> 4 | Subject: {subject} 5 | {message}""" 6 | print(template.format( 7 | from_email = "a@example.com", 8 | to_email = "b@example.com", 9 | message = "Here's some mail for you. " 10 | " Hope you enjoy the message!", 11 | subject = "You have mail!" 12 | )) 13 | 14 | -------------------------------------------------------------------------------- /Chapter8/8_07_unlabelled_kw.py: -------------------------------------------------------------------------------- 1 | print("{} {label} {}".format("x", "y", label="z")) 2 | -------------------------------------------------------------------------------- /Chapter8/8_08_tuple_dict_format.py: -------------------------------------------------------------------------------- 1 | emails = ("a@example.com", "b@example.com") 2 | message = { 3 | 'subject': "You Have Mail!", 4 | 'message': "Here's some mail for you!" 5 | } 6 | template = """ 7 | From: <{0[0]}> 8 | To: <{0[1]}> 9 | Subject: {message[subject]} 10 | {message[message]}""" 11 | print(template.format(emails, message=message)) 12 | 13 | 14 | -------------------------------------------------------------------------------- /Chapter8/8_09_tuple_in_dict_format.py: -------------------------------------------------------------------------------- 1 | emails = ("a@example.com", "b@example.com") 2 | message = { 3 | 'emails': emails, 4 | 'subject': "You Have Mail!", 5 | 'message': "Here's some mail for you!" 6 | } 7 | template = """ 8 | From: <{0[emails][0]}> 9 | To: <{0[emails][1]}> 10 | Subject: {0[subject]} 11 | {0[message]}""" 12 | print(template.format(message)) 13 | -------------------------------------------------------------------------------- /Chapter8/8_10_object_formatting.py: -------------------------------------------------------------------------------- 1 | class EMail: 2 | def __init__(self, from_addr, to_addr, subject, message): 3 | self.from_addr = from_addr 4 | self.to_addr = to_addr 5 | self.subject = subject 6 | self.message = message 7 | 8 | email = EMail("a@example.com", "b@example.com", 9 | "You Have Mail!", 10 | "Here's some mail for you!") 11 | 12 | template = """ 13 | From: <{0.from_addr}> 14 | To: <{0.to_addr}> 15 | Subject: {0.subject} 16 | 17 | {0.message}""" 18 | print(template.format(email)) 19 | -------------------------------------------------------------------------------- /Chapter8/8_11_no_format.py: -------------------------------------------------------------------------------- 1 | subtotal = 12.32 2 | tax = subtotal * 0.07 3 | total = subtotal + tax 4 | 5 | print("Sub: ${0} Tax: ${1} Total: ${total}".format( 6 | subtotal, tax, total=total)) 7 | -------------------------------------------------------------------------------- /Chapter8/8_12_currency_format.py: -------------------------------------------------------------------------------- 1 | subtotal = 12.32 2 | tax = subtotal * 0.07 3 | total = subtotal + tax 4 | 5 | print("Sub: ${0:0.2f} Tax: ${1:0.2f} " 6 | "Total: ${total:0.2f}".format( 7 | subtotal, tax, total=total)) 8 | -------------------------------------------------------------------------------- /Chapter8/8_13_tabular.py: -------------------------------------------------------------------------------- 1 | orders = [('burger', 2, 5), 2 | ('fries', 3.5, 1), 3 | ('cola', 1.75, 3)] 4 | 5 | print("PRODUCT QUANTITY PRICE SUBTOTAL") 6 | for product, price, quantity in orders: 7 | subtotal = price * quantity 8 | print("{0:10s}{1: ^9d} ${2: <8.2f}${3: >7.2f}".format( 9 | product, quantity, price, subtotal)) 10 | 11 | -------------------------------------------------------------------------------- /Chapter8/8_14_format_datetime.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | print("{0:%Y-%m-%d %I:%M%p }".format( 3 | datetime.datetime.now())) 4 | 5 | -------------------------------------------------------------------------------- /Chapter8/8_15_encode_bytes.py: -------------------------------------------------------------------------------- 1 | characters = b'\x63\x6c\x69\x63\x68\xe9' 2 | print(characters) 3 | print(characters.decode("latin-1")) 4 | -------------------------------------------------------------------------------- /Chapter8/8_16_decode_unicode.py: -------------------------------------------------------------------------------- 1 | characters = "cliché" 2 | print(characters.encode("UTF-8")) 3 | print(characters.encode("latin-1")) 4 | print(characters.encode("CP437")) 5 | print(characters.encode("ascii")) 6 | -------------------------------------------------------------------------------- /Chapter8/8_17_bytearray_replace.py: -------------------------------------------------------------------------------- 1 | b = bytearray(b"abcdefgh") 2 | b[4:6] = b"\x15\xa3" 3 | print(b) 4 | -------------------------------------------------------------------------------- /Chapter8/8_18_bytearray_index.py: -------------------------------------------------------------------------------- 1 | b = bytearray(b'abcdef') 2 | b[3] = ord(b'g') 3 | b[4] = 68 4 | print(b) 5 | -------------------------------------------------------------------------------- /Chapter8/8_23.1_basic_regex.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | search_string = "hello world" 4 | pattern = "hello world" 5 | 6 | match = re.match(pattern, search_string) 7 | 8 | if match: 9 | print("regex matches") 10 | -------------------------------------------------------------------------------- /Chapter8/8_23.2_regex_generic.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import re 3 | 4 | pattern = sys.argv[1] 5 | search_string = sys.argv[2] 6 | match = re.match(pattern, search_string) 7 | 8 | if match: 9 | template = "'{}' matches pattern '{}'" 10 | else: 11 | template = "'{}' does not match pattern '{}'" 12 | 13 | print(template.format(search_string, pattern)) 14 | -------------------------------------------------------------------------------- /Chapter8/8_23.3_match_group.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | pattern = "^[a-zA-Z.]+@([a-z.]*\.[a-z]+)$" 4 | search_string = "some.user@example.com" 5 | match = re.match(pattern, search_string) 6 | 7 | if match: 8 | domain = match.groups()[0] 9 | print(domain) 10 | -------------------------------------------------------------------------------- /Chapter8/8_23_stringio.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | from io import StringIO, BytesIO 3 | source_file = StringIO("an oft-repeated cliché") 4 | dest_file = BytesIO() 5 | 6 | char = source_file.read(1) 7 | while char: 8 | dest_file.write(char.encode("ascii", "replace")) 9 | char = source_file.read(1) 10 | 11 | print(dest_file.getvalue()) 12 | 13 | -------------------------------------------------------------------------------- /Chapter8/8_24_basic_pickling.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | 3 | some_data = ["a list", "containing", 5, 4 | "values including another list", 5 | ["inner", "list"]] 6 | 7 | with open("pickled_list", 'wb') as file: 8 | pickle.dump(some_data, file) 9 | 10 | with open("pickled_list", 'rb') as file: 11 | loaded_data = pickle.load(file) 12 | 13 | print(loaded_data) 14 | assert loaded_data == some_data 15 | -------------------------------------------------------------------------------- /Chapter8/8_25_state_pickling.py: -------------------------------------------------------------------------------- 1 | from threading import Timer 2 | import datetime 3 | from urllib.request import urlopen 4 | 5 | class UpdatedURL: 6 | def __init__(self, url): 7 | self.url = url 8 | self.contents = '' 9 | self.last_updated = None 10 | self.update() 11 | 12 | def update(self): 13 | self.contents = urlopen(self.url).read() 14 | self.last_updated = datetime.datetime.now() 15 | self.schedule() 16 | 17 | def schedule(self): 18 | self.timer = Timer(3600, self.update) 19 | self.timer.setDaemon(True) 20 | self.timer.start() 21 | 22 | def __getstate__(self): 23 | new_state = self.__dict__.copy() 24 | if 'timer' in new_state: 25 | del new_state['timer'] 26 | return new_state 27 | 28 | def __setstate__(self, data): 29 | self.__dict__ = data 30 | self.schedule() 31 | -------------------------------------------------------------------------------- /Chapter8/8_26_json_objects.py: -------------------------------------------------------------------------------- 1 | class Contact: 2 | def __init__(self, first, last): 3 | self.first = first 4 | self.last = last 5 | 6 | @property 7 | def full_name(self): 8 | return("{} {}".format(self.first, self.last)) 9 | 10 | 11 | import json 12 | class ContactEncoder(json.JSONEncoder): 13 | def default(self, obj): 14 | if isinstance(obj, Contact): 15 | return {'is_contact': True, 16 | 'first': obj.first, 17 | 'last': obj.last, 18 | 'full': obj.full_name} 19 | return super().default(obj) 20 | 21 | def decode_contact(dic): 22 | if dic.get('is_contact'): 23 | return Contact(dic['first'], dic['last']) 24 | else: 25 | return dic 26 | -------------------------------------------------------------------------------- /Chapter8/8_27_template_boilerplate.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import json 4 | from pathlib import Path 5 | 6 | DIRECTIVE_RE = re.compile( 7 | r'/\*\*\s*(include|variable|loopover|endloop|loopvar)' 8 | r'\s*([^ *]*)\s*\*\*/') 9 | 10 | 11 | class TemplateEngine: 12 | def __init__(self, infilename, outfilename, contextfilename): 13 | self.template = open(infilename).read() 14 | self.working_dir = Path(infilename).absolute().parent 15 | self.pos = 0 16 | self.outfile = open(outfilename, 'w') 17 | with open(contextfilename) as contextfile: 18 | self.context = json.load(contextfile) 19 | 20 | def process(self): 21 | print("PROCESSING...") 22 | 23 | 24 | if __name__ == '__main__': 25 | infilename, outfilename, contextfilename = sys.argv[1:] 26 | engine = TemplateEngine(infilename, outfilename, contextfilename) 27 | engine.process() 28 | -------------------------------------------------------------------------------- /Chapter8/8_28_template_process.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import json 4 | from pathlib import Path 5 | 6 | DIRECTIVE_RE = re.compile( 7 | r'/\*\*\s*(include|variable|loopover|endloop|loopvar)' 8 | r'\s*([^ *]*)\s*\*\*/') 9 | 10 | 11 | class TemplateEngine: 12 | def __init__(self, infilename, outfilename, contextfilename): 13 | self.template = open(infilename).read() 14 | self.working_dir = Path(infilename).absolute().parent 15 | self.pos = 0 16 | self.outfile = open(outfilename, 'w') 17 | with open(contextfilename) as contextfile: 18 | self.context = json.load(contextfile) 19 | 20 | def process(self): 21 | match = DIRECTIVE_RE.search(self.template, pos=self.pos) 22 | while match: 23 | self.outfile.write(self.template[self.pos:match.start()]) 24 | directive, argument = match.groups() 25 | self.pos = match.end() 26 | match = DIRECTIVE_RE.search(self.template, pos=self.pos) 27 | self.outfile.write(self.template[self.pos:]) 28 | 29 | if __name__ == '__main__': 30 | infilename, outfilename, contextfilename = sys.argv[1:] 31 | engine = TemplateEngine(infilename, outfilename, contextfilename) 32 | engine.process() 33 | -------------------------------------------------------------------------------- /Chapter8/8_29_template_processer_complete.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import json 4 | from pathlib import Path 5 | 6 | DIRECTIVE_RE = re.compile( 7 | r'/\*\*\s*(include|variable|loopover|endloop|loopvar)' 8 | r'\s*([^ *]*)\s*\*\*/') 9 | 10 | 11 | class TemplateEngine: 12 | def __init__(self, infilename, outfilename, contextfilename): 13 | self.template = open(infilename).read() 14 | self.working_dir = Path(infilename).absolute().parent 15 | self.pos = 0 16 | self.outfile = open(outfilename, 'w') 17 | with open(contextfilename) as contextfile: 18 | self.context = json.load(contextfile) 19 | 20 | def process(self): 21 | match = DIRECTIVE_RE.search(self.template, pos=self.pos) 22 | while match: 23 | self.outfile.write(self.template[self.pos:match.start()]) 24 | directive, argument = match.groups() 25 | method_name = 'process_{}'.format(directive) 26 | getattr(self, method_name)(match, argument) 27 | match = DIRECTIVE_RE.search(self.template, pos=self.pos) 28 | self.outfile.write(self.template[self.pos:]) 29 | 30 | def process_include(self, match, argument): 31 | with (self.working_dir / argument).open() as includefile: 32 | self.outfile.write(includefile.read()) 33 | self.pos = match.end() 34 | 35 | def process_variable(self, match, argument): 36 | self.outfile.write(self.context.get(argument, '')) 37 | self.pos = match.end() 38 | 39 | def process_loopover(self, match, argument): 40 | self.loop_index = 0 41 | self.loop_list = self.context.get(argument, []) 42 | self.pos = self.loop_pos = match.end() 43 | 44 | def process_loopvar(self, match, argument): 45 | self.outfile.write(self.loop_list[self.loop_index]) 46 | self.pos = match.end() 47 | 48 | def process_endloop(self, match, argument): 49 | self.loop_index += 1 50 | if self.loop_index >= len(self.loop_list): 51 | self.pos = match.end() 52 | del self.loop_index 53 | del self.loop_list 54 | del self.loop_pos 55 | else: 56 | self.pos = self.loop_pos 57 | 58 | if __name__ == '__main__': 59 | infilename, outfilename, contextfilename = sys.argv[1:] 60 | engine = TemplateEngine(infilename, outfilename, contextfilename) 61 | engine.process() 62 | -------------------------------------------------------------------------------- /Chapter8/case_study_input/context.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dusty", 3 | "book_list": [ 4 | "Thief Of Time", 5 | "The Thief", 6 | "Snow Crash", 7 | "Lathe Of Heaven" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /Chapter8/case_study_input/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Chapter8/case_study_input/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Chapter8/case_study_input/main.html: -------------------------------------------------------------------------------- 1 | /** include header.html **/ 2 |

This is the title of the front page

3 | /** include menu.html **/ 4 |

My name is /** variable name **/. 5 | This is the content of my front page. It goes below the menu.

6 | 7 | 8 | /** loopover book_list **/ 9 | 10 | /** endloop **/ 11 |
Favourite Books
/** loopvar **/
12 | /** include footer.html **/ 13 | Copyright © Today 14 | -------------------------------------------------------------------------------- /Chapter8/case_study_input/menu.html: -------------------------------------------------------------------------------- 1 |
First Link 2 | Second Link 3 | -------------------------------------------------------------------------------- /Chapter9/9_01_iterator.py: -------------------------------------------------------------------------------- 1 | 2 | class CapitalIterable: 3 | def __init__(self, string): 4 | self.string = string 5 | 6 | def __iter__(self): 7 | return CapitalIterator(self.string) 8 | 9 | 10 | class CapitalIterator: 11 | def __init__(self, string): 12 | self.words = [w.capitalize() for w in string.split()] 13 | self.index = 0 14 | 15 | def __next__(self): 16 | if self.index == len(self.words): 17 | raise StopIteration() 18 | 19 | word = self.words[self.index] 20 | self.index += 1 21 | return word 22 | 23 | def __iter__(self): 24 | return self 25 | -------------------------------------------------------------------------------- /Chapter9/9_06_for_loop_converter.py: -------------------------------------------------------------------------------- 1 | input_strings = ['1', '5', '28', '131', '3'] 2 | 3 | output_integers = [] 4 | for num in input_strings: 5 | output_integers.append(int(num)) 6 | -------------------------------------------------------------------------------- /Chapter9/9_07_list_comp_converter.py: -------------------------------------------------------------------------------- 1 | input_strings = ['1', '5', '28', '131', '3'] 2 | 3 | output_integers = [int(num) for num in input_strings] 4 | 5 | print(output_integers) 6 | -------------------------------------------------------------------------------- /Chapter9/9_08_list_comp_exclude.py: -------------------------------------------------------------------------------- 1 | input_strings = ['1', '5', '28', '131', '3'] 2 | 3 | output_integers = [int(n) for n in input_strings if len(n) < 3] 4 | -------------------------------------------------------------------------------- /Chapter9/9_09_tdf_list_comp.py: -------------------------------------------------------------------------------- 1 | import sys 2 | filename = sys.argv[1] 3 | 4 | with open(filename) as file: 5 | header = file.readline().strip().split('\t') 6 | contacts = [ 7 | dict( 8 | zip(header, line.strip().split('\t')) 9 | ) for line in file 10 | ] 11 | 12 | for contact in contacts: 13 | print("email: {email} -- {last}, {first}".format( 14 | **contact)) 15 | -------------------------------------------------------------------------------- /Chapter9/9_10_set_comprehension.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | Book = namedtuple("Book", "author title genre") 4 | books = [ 5 | Book("Pratchett", "Nightwatch", "fantasy"), 6 | Book("Pratchett", "Thief Of Time", "fantasy"), 7 | Book("Le Guin", "The Dispossessed", "scifi"), 8 | Book("Le Guin", "A Wizard Of Earthsea", "fantasy"), 9 | Book("Turner", "The Thief", "fantasy"), 10 | Book("Phillips", "Preston Diamond", "western"), 11 | Book("Phillips", "Twice Upon A Time", "scifi"), 12 | ] 13 | 14 | fantasy_authors = { 15 | b.author for b in books if b.genre == 'fantasy'} 16 | -------------------------------------------------------------------------------- /Chapter9/9_11_dict_comprehension.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | Book = namedtuple("Book", "author title genre") 4 | books = [ 5 | Book("Pratchett", "Nightwatch", "fantasy"), 6 | Book("Pratchett", "Thief Of Time", "fantasy"), 7 | Book("Le Guin", "The Dispossessed", "scifi"), 8 | Book("Le Guin", "A Wizard Of Earthsea", "fantasy"), 9 | Book("Turner", "The Thief", "fantasy"), 10 | Book("Phillips", "Preston Diamond", "western"), 11 | Book("Phillips", "Twice Upon A Time", "scifi"), 12 | ] 13 | 14 | fantasy_titles = { 15 | b.title: b for b in books if b.genre == 'fantasy'} 16 | -------------------------------------------------------------------------------- /Chapter9/9_12_log_file.log: -------------------------------------------------------------------------------- 1 | Jan 26, 2010 11:25:25 DEBUG This is a debugging message. 2 | Jan 26, 2010 11:25:36 INFO This is an information method. 3 | Jan 26, 2010 11:25:46 WARNING This is a warning. It could be serious. 4 | Jan 26, 2010 11:25:52 WARNING Another warning sent. 5 | Jan 26, 2010 11:25:59 INFO Here's some information. 6 | Jan 26, 2010 11:26:13 DEBUG Debug messages are only useful if you want to figure something out. 7 | Jan 26, 2010 11:26:32 INFO Information is usually harmless, but helpful. 8 | Jan 26, 2010 11:26:40 WARNING Warnings should be heeded. 9 | Jan 26, 2010 11:26:54 WARNING Watch for warnings. 10 | -------------------------------------------------------------------------------- /Chapter9/9_13_log_processor.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | inname = sys.argv[1] 4 | outname = sys.argv[2] 5 | 6 | with open(inname) as infile: 7 | with open(outname, "w") as outfile: 8 | warnings = (l for l in infile if 'WARNING' in l) 9 | for l in warnings: 10 | outfile.write(l) 11 | 12 | -------------------------------------------------------------------------------- /Chapter9/9_14_log_delete_warning_expression.py: -------------------------------------------------------------------------------- 1 | import sys 2 | inname, outname = sys.argv[1:3] 3 | 4 | with open(inname) as infile: 5 | with open(outname, "w") as outfile: 6 | warnings = (l.replace('\tWARNING', '') 7 | for l in infile if 'WARNING' in l) 8 | for l in warnings: 9 | outfile.write(l) 10 | 11 | -------------------------------------------------------------------------------- /Chapter9/9_15_log_delete_warnings_loop.py: -------------------------------------------------------------------------------- 1 | import sys 2 | inname, outname = sys.argv[1:3] 3 | 4 | with open(inname) as infile: 5 | with open(outname, "w") as outfile: 6 | for l in infile: 7 | if 'WARNING' in l: 8 | outfile.write(l.replace('\tWARNING', '')) 9 | 10 | -------------------------------------------------------------------------------- /Chapter9/9_16_log_delete_warnings_object.py: -------------------------------------------------------------------------------- 1 | import sys 2 | inname, outname = sys.argv[1:3] 3 | 4 | class WarningFilter: 5 | def __init__(self, insequence): 6 | self.insequence = insequence 7 | def __iter__(self): 8 | return self 9 | def __next__(self): 10 | l = self.insequence.readline() 11 | while l and 'WARNING' not in l: 12 | l = self.insequence.readline() 13 | if not l: 14 | raise StopIteration 15 | return l.replace('\tWARNING', '') 16 | 17 | with open(inname) as infile: 18 | with open(outname, "w") as outfile: 19 | filter = WarningFilter(infile) 20 | for l in filter: 21 | outfile.write(l) 22 | 23 | -------------------------------------------------------------------------------- /Chapter9/9_17_log_delete_warnings_generator.py: -------------------------------------------------------------------------------- 1 | import sys 2 | inname, outname = sys.argv[1:3] 3 | 4 | def warnings_filter(insequence): 5 | for l in insequence: 6 | if 'WARNING' in l: 7 | yield l.replace('\tWARNING', '') 8 | 9 | with open(inname) as infile: 10 | with open(outname, "w") as outfile: 11 | filter = warnings_filter(infile) 12 | for l in filter: 13 | outfile.write(l) 14 | -------------------------------------------------------------------------------- /Chapter9/9_18_log_delete_warnings_yield_from.py: -------------------------------------------------------------------------------- 1 | import sys 2 | inname, outname = sys.argv[1:3] 3 | 4 | 5 | def warnings_filter(infilename): 6 | with open(infilename) as infile: 7 | yield from ( 8 | l.replace('\tWARNING', '') 9 | for l in infile 10 | if 'WARNING' in l 11 | ) 12 | 13 | filter = warnings_filter(inname) 14 | with open(outname, "w") as outfile: 15 | for l in filter: 16 | outfile.write(l) 17 | -------------------------------------------------------------------------------- /Chapter9/9_19_yield_from_filesystem.py: -------------------------------------------------------------------------------- 1 | 2 | class File: 3 | def __init__(self, name): 4 | self.name = name 5 | 6 | 7 | class Folder(File): 8 | def __init__(self, name): 9 | super().__init__(name) 10 | self.children = [] 11 | 12 | root = Folder('') 13 | etc = Folder('etc') 14 | root.children.append(etc) 15 | etc.children.append(File('passwd')) 16 | etc.children.append(File('groups')) 17 | httpd = Folder('httpd') 18 | etc.children.append(httpd) 19 | httpd.children.append(File('http.conf')) 20 | var = Folder('var') 21 | root.children.append(var) 22 | log = Folder('log') 23 | var.children.append(log) 24 | log.children.append(File('messages')) 25 | log.children.append(File('kernel')) 26 | 27 | 28 | def walk(file): 29 | if isinstance(file, Folder): 30 | yield file.name + '/' 31 | for f in file.children: 32 | yield from walk(f) 33 | else: 34 | yield file.name 35 | 36 | for filename in walk(root): 37 | print(filename) 38 | -------------------------------------------------------------------------------- /Chapter9/9_20_basic_count_coroutine.py: -------------------------------------------------------------------------------- 1 | 2 | def tally(): 3 | score = 0 4 | while True: 5 | increment = yield score 6 | score += increment 7 | -------------------------------------------------------------------------------- /Chapter9/9_21_kernel_log.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def match_regex(filename, regex): 5 | with open(filename) as file: 6 | lines = file.readlines() 7 | for line in reversed(lines): 8 | match = re.match(regex, line) 9 | if match: 10 | regex = yield match.groups()[0] 11 | 12 | 13 | def get_serials(filename): 14 | ERROR_RE = 'XFS ERROR (\[sd[a-z]\])' 15 | matcher = match_regex(filename, ERROR_RE) 16 | device = next(matcher) 17 | while True: 18 | bus = matcher.send('(sd \S+) {}.*'.format(re.escape(device))) 19 | serial = matcher.send('{} \(SERIAL=([^)]*)\)'.format(bus)) 20 | yield serial 21 | device = matcher.send(ERROR_RE) 22 | 23 | 24 | for serial_number in get_serials('EXAMPLE_LOG.log'): 25 | print(serial_number) 26 | -------------------------------------------------------------------------------- /Chapter9/9_22_load_dataset.py: -------------------------------------------------------------------------------- 1 | import csv 2 | 3 | dataset_filename = 'colors.csv' 4 | 5 | 6 | def load_colors(filename): 7 | with open(filename) as dataset_file: 8 | lines = csv.reader(dataset_file) 9 | for line in lines: 10 | yield tuple(float(y) for y in line[0:3]), line[3] 11 | 12 | for color, name in load_colors(dataset_filename): 13 | print("RGB {} is named {}".format(color, name)) 14 | -------------------------------------------------------------------------------- /Chapter9/9_23_generate_colors.py: -------------------------------------------------------------------------------- 1 | from random import random 2 | 3 | 4 | def generate_colors(count=100): 5 | for i in range(count): 6 | yield (random(), random(), random()) 7 | 8 | for color in generate_colors(): 9 | print(color) 10 | -------------------------------------------------------------------------------- /Chapter9/9_24_color_distance.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | 4 | def color_distance(color1, color2): 5 | channels = zip(color1, color2) 6 | sum_distance_squared = 0 7 | for c1, c2 in channels: 8 | sum_distance_squared += (c1 - c2) ** 2 9 | return math.sqrt(sum_distance_squared) 10 | 11 | print(color_distance((1, 1), (4, 5))) 12 | -------------------------------------------------------------------------------- /Chapter9/9_25_knearest.py: -------------------------------------------------------------------------------- 1 | import csv 2 | from random import random 3 | import math 4 | 5 | dataset_filename = 'colors.csv' 6 | 7 | 8 | def load_colors(filename): 9 | with open(filename) as dataset_file: 10 | lines = csv.reader(dataset_file) 11 | for line in lines: 12 | yield tuple(float(y) for y in line[0:3]), line[3] 13 | 14 | 15 | def generate_colors(count=100): 16 | for i in range(count): 17 | yield (random(), random(), random()) 18 | 19 | 20 | def color_distance(color1, color2): 21 | channels = zip(color1, color2) 22 | sum_distance_squared = 0 23 | for c1, c2 in channels: 24 | sum_distance_squared += (c1 - c2) ** 2 25 | return math.sqrt(sum_distance_squared) 26 | 27 | 28 | def nearest_neighbors(model_colors, num_neighbors): 29 | model = list(model_colors) 30 | target = yield 31 | while True: 32 | distances = sorted( 33 | ((color_distance(c[0], target), c) for c in model), 34 | ) 35 | target = yield [ 36 | d[1] for d in distances[0:num_neighbors] 37 | ] 38 | 39 | 40 | model_colors = load_colors(dataset_filename) 41 | target_colors = generate_colors(3) 42 | get_neighbors = nearest_neighbors(model_colors, 5) 43 | next(get_neighbors) 44 | 45 | for color in target_colors: 46 | distances = get_neighbors.send(color) 47 | print(color) 48 | for d in distances: 49 | print(color_distance(color, d[0]), d[1]) 50 | -------------------------------------------------------------------------------- /Chapter9/9_26_write_results.py: -------------------------------------------------------------------------------- 1 | import csv 2 | 3 | 4 | def write_results(filename="output.csv"): 5 | with open(filename, "w") as file: 6 | writer = csv.writer(file) 7 | while True: 8 | color, name = yield 9 | writer.writerow(list(color) + [name]) 10 | 11 | results = write_results() 12 | next(results) 13 | for i in range(3): 14 | print(i) 15 | results.send(((i, i, i), i * 10)) 16 | -------------------------------------------------------------------------------- /Chapter9/EXAMPLE_LOG.log: -------------------------------------------------------------------------------- 1 | unrelated log messages 2 | sd 0:0:0:0 Attached Disk Drive 3 | unrelated log messages 4 | sd 0:0:0:0 (SERIAL=ZZ12345) 5 | unrelated log messages 6 | sd 0:0:0:0 [sda] Options 7 | unrelated log messages 8 | XFS ERROR [sda] 9 | unrelated log messages 10 | sd 2:0:0:1 Attached Disk Drive 11 | unrelated log messages 12 | sd 2:0:0:1 (SERIAL=ZZ67890) 13 | unrelated log messages 14 | sd 2:0:0:1 [sdb] Options 15 | unrelated log messages 16 | sd 3:0:1:8 Attached Disk Drive 17 | unrelated log messages 18 | sd 3:0:1:8 (SERIAL=WW11111) 19 | unrelated log messages 20 | sd 3:0:1:8 [sdc] Options 21 | unrelated log messages 22 | XFS ERROR [sdc] 23 | unrelated log messages 24 | -------------------------------------------------------------------------------- /Chapter9/case_study_machine_learn.py: -------------------------------------------------------------------------------- 1 | import csv 2 | from random import random 3 | import math 4 | from collections import Counter 5 | 6 | dataset_filename = 'colors.csv' 7 | 8 | 9 | def load_colors(filename): 10 | with open(filename) as dataset_file: 11 | lines = csv.reader(dataset_file) 12 | for line in lines: 13 | yield tuple(float(y) for y in line[0:3]), line[3] 14 | 15 | 16 | def generate_colors(count=100): 17 | for i in range(count): 18 | yield (random(), random(), random()) 19 | 20 | 21 | def color_distance(color1, color2): 22 | channels = zip(color1, color2) 23 | sum_distance_squared = 0 24 | for c1, c2 in channels: 25 | sum_distance_squared += (c1 - c2) ** 2 26 | return math.sqrt(sum_distance_squared) 27 | 28 | 29 | def nearest_neighbors(model_colors, num_neighbors): 30 | model = list(model_colors) 31 | target = yield 32 | while True: 33 | distances = sorted( 34 | ((color_distance(c[0], target), c) for c in model), 35 | ) 36 | target = yield [ 37 | d[1] for d in distances[0:num_neighbors] 38 | ] 39 | 40 | 41 | def name_colors(get_neighbors): 42 | color = yield 43 | while True: 44 | near = get_neighbors.send(color) 45 | name_guess = Counter(n[1] for n in near).most_common(1)[0][0] 46 | color = yield name_guess 47 | 48 | 49 | def write_results(filename="output.csv"): 50 | with open(filename, "w") as file: 51 | writer = csv.writer(file) 52 | while True: 53 | color, name = yield 54 | writer.writerow(list(color) + [name]) 55 | 56 | 57 | def process_colors(dataset_filename="colors.csv"): 58 | model_colors = load_colors(dataset_filename) 59 | get_neighbors = nearest_neighbors(model_colors, 5) 60 | get_color_name = name_colors(get_neighbors) 61 | output = write_results() 62 | next(output) 63 | next(get_neighbors) 64 | next(get_color_name) 65 | 66 | for color in generate_colors(): 67 | name = get_color_name.send(color) 68 | output.send((color, name)) 69 | 70 | process_colors() 71 | -------------------------------------------------------------------------------- /Chapter9/example.log: -------------------------------------------------------------------------------- 1 | Jan 26, 2015 11:25:25 DEBUG This is a debugging message. 2 | Jan 26, 2015 11:25:36 INFO This is an information method. 3 | Jan 26, 2015 11:25:46 WARNING This is a warning. It could be serious. 4 | Jan 26, 2015 11:25:52 WARNING Another warning sent. 5 | Jan 26, 2015 11:25:59 INFO Here's some information. 6 | Jan 26, 2015 11:26:13 DEBUG Debug messages are only useful if you want to figure something out. 7 | Jan 26, 2015 11:26:32 INFO Information is usually harmless, but helpful. 8 | Jan 26, 2015 11:26:40 WARNING Warnings should be heeded. 9 | Jan 26, 2015 11:26:54 WARNING Watch for warnings. 10 | -------------------------------------------------------------------------------- /Chapter9/kivy_color_checker/colorchecker.kv: -------------------------------------------------------------------------------- 1 | BoxLayout: 2 | colorbox: colorbox 3 | BoxLayout: 4 | orientation: "vertical" 5 | ColorBox: 6 | id: colorbox 7 | size_hint_y: 2 8 | Label: 9 | text: app.color_name 10 | 11 | BoxLayout: 12 | orientation: "vertical" 13 | Button: 14 | text: "Yes" 15 | on_press: app.process_response(True) 16 | Button: 17 | text: "No" 18 | on_press: app.process_response(False) 19 | Label: 20 | text: "{:.2f}%".format(app.score) 21 | 22 | : 23 | color: app.color 24 | canvas: 25 | Color: 26 | rgb: root.color 27 | Rectangle: 28 | pos: root.pos 29 | size: root.size 30 | -------------------------------------------------------------------------------- /Chapter9/kivy_color_checker/main.py: -------------------------------------------------------------------------------- 1 | from random import random 2 | import csv 3 | 4 | from kivy.app import App 5 | from kivy.uix.widget import Widget 6 | from kivy.properties import ListProperty, NumericProperty, StringProperty 7 | 8 | 9 | class ColorBox(Widget): 10 | color = ListProperty([random(), random(), random()]) 11 | 12 | 13 | class ColorCheckerApp(App): 14 | score = NumericProperty() 15 | color_name = StringProperty() 16 | color = ListProperty([0, 0, 0]) 17 | total = NumericProperty() 18 | correct = NumericProperty() 19 | 20 | def __init__(self): 21 | super(ColorCheckerApp, self).__init__() 22 | self.file = csv.reader(open("output.csv", "r")) 23 | self.advance_color() 24 | 25 | def advance_color(self): 26 | try: 27 | r, g, b, name = next(self.file) 28 | except StopIteration: 29 | self.root.disabled = True 30 | else: 31 | self.color = float(r), float(g), float(b) 32 | self.color_name = name 33 | 34 | def process_response(self, is_correct): 35 | if is_correct: 36 | self.correct += 1 37 | self.total += 1 38 | self.score = 100.0 * self.correct / self.total 39 | self.advance_color() 40 | 41 | 42 | ColorCheckerApp().run() 43 | -------------------------------------------------------------------------------- /Chapter9/kivy_color_classifier/colorclassifier.kv: -------------------------------------------------------------------------------- 1 | BoxLayout: 2 | colorbox: colorbox 3 | ColorBox: 4 | id: colorbox 5 | GridLayout: 6 | cols: 3 7 | ColorName: 8 | text: "Red" 9 | ColorName: 10 | text: "Purple" 11 | ColorName: 12 | text: "Blue" 13 | ColorName: 14 | text: "Green" 15 | ColorName: 16 | text: "Yellow" 17 | ColorName: 18 | text: "Orange" 19 | ColorName: 20 | text: "Grey" 21 | ColorName: 22 | text: "White" 23 | ColorName: 24 | text: "Pink" 25 | 26 | : 27 | canvas: 28 | Color: 29 | rgb: root.color 30 | Rectangle: 31 | pos: root.pos 32 | size: root.size 33 | 34 | : 35 | on_press: app.store_color(self.text) 36 | -------------------------------------------------------------------------------- /Chapter9/kivy_color_classifier/main.py: -------------------------------------------------------------------------------- 1 | from random import random 2 | import csv 3 | 4 | from kivy.app import App 5 | from kivy.uix.widget import Widget 6 | from kivy.properties import ListProperty 7 | 8 | 9 | class ColorBox(Widget): 10 | color = ListProperty([random(), random(), random()]) 11 | 12 | 13 | class ColorClassifierApp(App): 14 | def __init__(self): 15 | super(ColorClassifierApp, self).__init__() 16 | self.file = csv.writer(open("colors.csv", "a")) 17 | 18 | def store_color(self, color_name): 19 | self.file.writerow(self.root.colorbox.color + [color_name]) 20 | self.root.colorbox.color = [random(), random(), random()] 21 | 22 | ColorClassifierApp().run() 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | --------------------------------------------------------------------------------