├── .gitattributes ├── Chapter6 ├── 6_01_empty_object.py ├── 6_15_stupid_adding_integer.py ├── 6_04_dict_stocks.py ├── 6_09_counter_frequency.py ├── 6_03_named_tuple.py ├── 6_20_collect_remaining_links.py ├── 6_21_print_collected_links.py ├── case_study_serve │ ├── yoga.html │ ├── contact.html │ ├── blog.html │ ├── index.html │ ├── hobbies.html │ └── esme.html ├── 6_08_defaultdict_custom_function.py ├── 6_07_defaultdict_frequency.py ├── 6_06_setdefault_frequency.py ├── 6_02_pass_tuple_to_function.py ├── 6_14_oop_pairs.py ├── 6_18_normalize_url.py ├── 6_09_list_tuple_frequency.py ├── 6_09_counter_poll.py ├── 6_12_set_operations.py ├── 6_11_song_artist_set.py ├── 6_17_link_parser.py ├── 6_13_set_operations2.py ├── 6_05_random_key_dict.py ├── 6_10_object_comparison.py ├── 6_16_dictsorted.py ├── 6_19_visited_links_sets.py ├── 6_22_dict_link_collector.py ├── link_collector.py └── 6_23_queue_link_collector.py ├── Chapter8 ├── case_study_input │ ├── header.html │ ├── footer.html │ ├── menu.html │ ├── context.json │ └── main.html ├── 8_07_unlabelled_kw.py ├── 8_17_bytearray_replace.py ├── 8_18_bytearray_index.py ├── 8_02_format_empty.py ├── 8_14_format_datetime.py ├── 8_03_format_position.py ├── 8_15_encode_bytes.py ├── 8_04_format_some_positions_broken.py ├── 8_01_string_creation.py ├── 8_11_no_format.py ├── 8_16_decode_unicode.py ├── 8_23.1_basic_regex.py ├── 8_12_currency_format.py ├── 8_05_brace_escape.py ├── 8_23.3_match_group.py ├── 8_23_stringio.py ├── 8_23.2_regex_generic.py ├── 8_13_tabular.py ├── 8_06_format_kw_args.py ├── 8_08_tuple_dict_format.py ├── 8_09_tuple_in_dict_format.py ├── 8_24_basic_pickling.py ├── 8_10_object_formatting.py ├── 8_26_json_objects.py ├── 8_25_state_pickling.py ├── 8_27_template_boilerplate.py ├── 8_28_template_process.py └── 8_29_template_processer_complete.py ├── Chapter7 ├── 7_05_read_file.py ├── 7_07_with.py ├── addresses.db ├── 7_06_write.py ├── timer.pyc ├── 7_04_tdf_contact.txt ├── 7_20_link_downloader_vararg.py ├── 7_36_enter_exit.py ├── 7_18_bad_kw_default.py ├── __pycache__ │ └── timer.cpython-34.pyc ├── 7_03.1_zip_to_enumerate.py ├── 7_02_enumerate_line_numbers.py ├── 7_03_enumerate_max_min.py ├── 7_27_add_function_to_object.py ├── 7_33_mailing_list_get_emails.py ├── 7_34_send_mailing.py ├── 7_32_mailing_list_defaultdict_set.py ├── 7_19_link_downloader.py ├── 7_23_unpacking_arguments.py ├── 7_08_context_manager.py ├── 7_05_tdf_processor.py ├── 7_21_kwarg_options.py ├── 7_01_reversible_objects.py ├── 7_28_callable_repeat.py ├── 7_29_send_email.py ├── 7_35_load_save.py ├── 7_24_function_object.py ├── 7_30_send_email_dict_headers.py ├── timer.py ├── 7_25_timer.py ├── 7_22_all_arguments.py ├── 7_26_timer_test.py └── mailing_list.py ├── Chapter12 ├── 12_06_simplestpytest.py ├── dump.rdb ├── stats.pyc ├── 12_09_funcargs.pyc ├── 12_07_class_pytest.pyc ├── 12_12_pytest_echo.pyc ├── 12_06_simplestpytest.pyc ├── 12_08_setup_teardown.pyc ├── 12_10_funcarg_finalizer.pyc ├── 12_15_pytest_skipifmark.pyc ├── 12_13_pytest_simple_skip.pyc ├── 12_14_pytest_importorskip.pyc ├── 12_15_pytest_skipifmark.py ├── casestudy │ ├── vigenere_cipher.pyc │ ├── test_vigenere_cipher.pyc │ ├── __pycache__ │ │ ├── vigenere_cipher.cpython-34.pyc │ │ └── test_vigenere_cipher.cpython-34-PYTEST.pyc │ ├── vigenere_cipher.py │ ├── vigenere_cipher1.py │ └── test_vigenere_cipher.py ├── 12_07_class_pytest.py ├── __pycache__ │ ├── stats.cpython-34.pyc │ └── test_mock.cpython-34-PYTEST.pyc ├── 12_14_pytest_importorskip.py ├── 12_13_pytest_simple_skip.py ├── 12_30_coverage_unittest.py ├── 12_01_simplest_unittest.py ├── 12_11_echo_server.py ├── 12_02_assertraises_python.py ├── 12_09_funcargs.py ├── 12_10_funcarg_finalizer.py ├── 12_04_test_stats.py ├── test_mock.py ├── stats.py ├── 12_03_stats.py ├── 12_05_skipping_tests.py ├── 12_17_mock_redis.py ├── 12_12_pytest_echo.py ├── 12_08_setup_teardown.py └── 12_16_mock_flightstatus.py ├── Chapter3 ├── 3_01_inheriting_from_object.py ├── 3_17.1_abc_container.py ├── 3_26_rudimentary_agent.py ├── 3_08_send_mail.py ├── 3_10_friend_address_holder.py ├── 3_09_send_mail_multi.py ├── 3_02_simple_contact_class_to_inherit_from.py ├── 3_24_house_rental.py ├── 3_05_dictionary_long_name.py ├── 3_20_validation_function.py ├── 3_11_friend_multi.py ├── 3_17_ducktype_flac.py ├── 3_07_friend_overrides_init_super.py ├── 3_06_friend_overrides_init.py ├── 3_03_contact_inherit_supplier.py ├── 3_27_type_map.py ├── 3_17.2_abc_media.py ├── 3_04_contact_list_inheritance.py ├── 3_16_polymorphic_audio.py ├── 3_15_friend_multi_super.py ├── 3_25_remaining_subclasses.py ├── 3_14_contrived_diamond_super.py ├── 3_18_property.py ├── 3_12_contrived_diamond.py ├── 3_28_add_property.py ├── 3_21_apartment_nice_prompt.py ├── 3_22_house.py ├── 3_19_apartment_ugly_prompt.py └── 3_23_purchase_and_rental.py ├── Chapter10 ├── 10_10_decorator_syntax.py ├── Benson.jpg ├── TILED.jpg ├── sales.db ├── 10_03_simple_client.py ├── 10_06_calling_decorated_sockets.py ├── 10_16_singleton_using_new.py ├── 10_02_simple_socket.py ├── 10_19_template_abstract_noimple.py ├── 10_14_simple_xml_to_parse.xml ├── 10_04_logging_decorator.py ├── 10_09_logging_decorator.py ├── 10_11_observer_core.py ├── 10_05_gzip_decorator.py ├── 10_18_create_database_for_template.py ├── 10_20_template_abstract_implemented.py ├── 10_12_observer_observing.py ├── 10_21_template_concretes.py ├── 10_13_strategy_tile.py ├── 10_15_xml_states.py └── 10_17_xml_singletonstates.py ├── Chapter5 ├── 5_09_property_decorator_get.py ├── 5_27_string_property.py ├── 5_16_average_property.py ├── zip_processor.pyc ├── 5_12_property_decorator_get_set.py ├── 5_13_read_only_setattr.py ├── 5_14_read_only_getattribute.py ├── 5_05_python_pretty_as_python.py ├── 5_04_pytho_ugly_as_java.py ├── 5_01_distances_no_objects.py ├── 5_06_setting_name_in_method.py ├── 5_15_cache_getter.py ├── 5_07_setting_name_property.py ├── 5_11_property_decorator_arguments.py ├── 5_08_property_arguments.py ├── 5_20_scaleimage_inheritance.py ├── 5_28_Character_class.py ├── 5_24_most_basic_document.py ├── 5_02_distances_by_object.py ├── 5_25_document_cursor.py ├── 5_03_object_polygon_init.py ├── zip_processor.py ├── 5_18_zipprocessor.py ├── 5_19_zipreplace_inheritance.py ├── 5_26_document_using_cursor.py ├── 5_17_zipsearch.py ├── Document.py └── 5_29_document_with_character.py ├── Chapter4 ├── auth.pyc ├── 4_10_defining_an_exception.py ├── 4_08_catch_as_keyword.py ├── __pycache__ │ └── auth.cpython-34.pyc ├── 4_02_exception_quits.py ├── 4_05_catch_specific_exception.py ├── 4_01_even_integers.py ├── 4_04_try_except.py ├── 4_11_exception_with_custom_args.py ├── 04_03_method_calls_excepting.py ├── 4_06_catch_multiple_exceptions.py ├── 4_07_catch_multiple_different.py ├── 4_12_handle_custom_exception.py ├── 4_09_finally_and_else.py ├── 4_13_branching_vs_exceptions.py ├── 4_15_inventory_handling.py ├── 4_14_inventory_mock_object.py ├── 4_16_auth_user.py ├── 4_17_authenticator.py ├── 4_18_login.py └── 4_20_test_auth.py ├── Chapter9 ├── 9_08_list_comp_exclude.py ├── 9_20_basic_count_coroutine.py ├── 9_07_list_comp_converter.py ├── 9_06_for_loop_converter.py ├── 9_23_generate_colors.py ├── 9_15_log_delete_warnings_loop.py ├── 9_13_log_processor.py ├── 9_24_color_distance.py ├── 9_14_log_delete_warning_expression.py ├── 9_22_load_dataset.py ├── 9_26_write_results.py ├── 9_09_tdf_list_comp.py ├── 9_17_log_delete_warnings_generator.py ├── 9_18_log_delete_warnings_yield_from.py ├── example.log ├── 9_12_log_file.log ├── 9_01_iterator.py ├── 9_10_set_comprehension.py ├── 9_11_dict_comprehension.py ├── EXAMPLE_LOG.log ├── 9_16_log_delete_warnings_object.py ├── kivy_color_classifier │ ├── main.py │ └── colorclassifier.kv ├── 9_21_kernel_log.py ├── kivy_color_checker │ ├── colorchecker.kv │ └── main.py ├── 9_19_yield_from_filesystem.py ├── 9_25_knearest.py └── case_study_machine_learn.py ├── Chapter13 ├── case_study │ ├── row.bmp │ ├── big.bmp │ ├── big2.bmp │ ├── big3.bmp │ ├── big4.bmp │ ├── big5.bmp │ ├── big6.bmp │ ├── big7.bmp │ ├── big8.bmp │ ├── bricks.bmp │ ├── decompress_to_bmp.py │ └── compress_bmp.py ├── 13_01_two_basic_threads.py ├── 13_03_parallel.py ├── 13_07_basic_async.py ├── 13_04_prime_factor.py ├── 13_10_async_client.py ├── 13_06_futures.py ├── 13_05_post_search.py ├── 13_02_thread_wait.py ├── 13_09_async_multiprocessing.py └── 13_08_asyncio_dns.py ├── Chapter11 ├── 11_12_window_command_function.py ├── 11_05_flyweight_factory.py ├── 11_09_window_commands.py ├── 11_01_age_calculator.py ├── 11_03_age_calculator_adapt_date.py ├── 11_16_composite_folder_methods.py ├── 11_13_document_command_callable.py ├── 11_02_age_calculator_adapted.py ├── 11_06_flyweight_init.py ├── 11_10_window_command_invokers.py ├── 11_17_component_hierarchy.py ├── 11_18_add_child.py ├── 11_07_flyweight_check_serial.py ├── 11_04_email_facade.py ├── 11_08_car_class.py ├── 11_11_window_command_commands.py ├── 11_14_formatters.py └── 11_15_formatter_factories.py ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.bmp filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /Chapter6/6_01_empty_object.py: -------------------------------------------------------------------------------- 1 | class MyObject: 2 | pass 3 | -------------------------------------------------------------------------------- /Chapter8/case_study_input/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Chapter8/case_study_input/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Chapter7/7_05_read_file.py: -------------------------------------------------------------------------------- 1 | file = open('filename') 2 | print(file.read()) 3 | -------------------------------------------------------------------------------- /Chapter12/12_06_simplestpytest.py: -------------------------------------------------------------------------------- 1 | def test_int_float(): 2 | assert 1 == 1.0 3 | -------------------------------------------------------------------------------- /Chapter3/3_01_inheriting_from_object.py: -------------------------------------------------------------------------------- 1 | class MySubClass(object): 2 | pass 3 | -------------------------------------------------------------------------------- /Chapter8/8_07_unlabelled_kw.py: -------------------------------------------------------------------------------- 1 | print("{} {label} {}".format("x", "y", label="z")) 2 | -------------------------------------------------------------------------------- /Chapter8/8_17_bytearray_replace.py: -------------------------------------------------------------------------------- 1 | b = bytearray(b"abcdefgh") 2 | b[4:6] = b"\x15\xa3" 3 | print(b) 4 | -------------------------------------------------------------------------------- /Chapter10/10_10_decorator_syntax.py: -------------------------------------------------------------------------------- 1 | @log_calls 2 | def test1(a,b,c): 3 | print("\ttest1 called") 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 | -------------------------------------------------------------------------------- /Chapter6/6_15_stupid_adding_integer.py: -------------------------------------------------------------------------------- 1 | class SillyInt(int): 2 | def __add__(self, num): 3 | return 0 4 | -------------------------------------------------------------------------------- /Chapter7/7_07_with.py: -------------------------------------------------------------------------------- 1 | with open('filename') as file: 2 | for line in file: 3 | print(line, end='') 4 | -------------------------------------------------------------------------------- /Chapter8/case_study_input/menu.html: -------------------------------------------------------------------------------- 1 | First Link 2 | Second Link 3 | -------------------------------------------------------------------------------- /Chapter5/5_09_property_decorator_get.py: -------------------------------------------------------------------------------- 1 | class Foo: 2 | @property 3 | def foo(self): 4 | return "bar" 5 | -------------------------------------------------------------------------------- /Chapter5/5_27_string_property.py: -------------------------------------------------------------------------------- 1 | @property 2 | def string(self): 3 | return "".join(self.characters) 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 | -------------------------------------------------------------------------------- /Chapter7/addresses.db: -------------------------------------------------------------------------------- 1 | friend1@example.com friends 2 | friend2@example.com friends 3 | family1@example.com family,friends 4 | -------------------------------------------------------------------------------- /Chapter8/8_02_format_empty.py: -------------------------------------------------------------------------------- 1 | template = "Hello {}, you are currently {}." 2 | print(template.format('Dusty', 'writing')) 3 | -------------------------------------------------------------------------------- /Chapter7/7_06_write.py: -------------------------------------------------------------------------------- 1 | contents = "Some file contents" 2 | file = open("filename", "w") 3 | file.write(contents) 4 | file.close() 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Chapter4/auth.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter4/auth.pyc -------------------------------------------------------------------------------- /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_15_encode_bytes.py: -------------------------------------------------------------------------------- 1 | characters = b'\x63\x6c\x69\x63\x68\xe9' 2 | print(characters) 3 | print(characters.decode("latin-1")) 4 | -------------------------------------------------------------------------------- /Chapter10/Benson.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter10/Benson.jpg -------------------------------------------------------------------------------- /Chapter10/TILED.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter10/TILED.jpg -------------------------------------------------------------------------------- /Chapter10/sales.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter10/sales.db -------------------------------------------------------------------------------- /Chapter12/dump.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter12/dump.rdb -------------------------------------------------------------------------------- /Chapter12/stats.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter12/stats.pyc -------------------------------------------------------------------------------- /Chapter6/6_09_counter_frequency.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | def letter_frequency(sentence): 3 | return Counter(sentence) 4 | -------------------------------------------------------------------------------- /Chapter7/timer.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter7/timer.pyc -------------------------------------------------------------------------------- /Chapter5/5_16_average_property.py: -------------------------------------------------------------------------------- 1 | class AverageList(list): 2 | @property 3 | def average(self): 4 | return sum(self) / len(self) 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Chapter8/8_04_format_some_positions_broken.py: -------------------------------------------------------------------------------- 1 | template = "Hello {}, you are {}. Your name is {0}." 2 | print(template.format('Dusty', 'writing')) 3 | -------------------------------------------------------------------------------- /Chapter5/zip_processor.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter5/zip_processor.pyc -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Chapter12/12_09_funcargs.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter12/12_09_funcargs.pyc -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Chapter12/12_07_class_pytest.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter12/12_07_class_pytest.pyc -------------------------------------------------------------------------------- /Chapter12/12_12_pytest_echo.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter12/12_12_pytest_echo.pyc -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Chapter12/12_06_simplestpytest.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter12/12_06_simplestpytest.pyc -------------------------------------------------------------------------------- /Chapter12/12_08_setup_teardown.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter12/12_08_setup_teardown.pyc -------------------------------------------------------------------------------- /Chapter13/case_study/row.bmp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1e7b82cb4945f230f808b9b21ebc14dffb7e60be6fea68a877960d18ac80b5b7 3 | size 1322 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Chapter12/12_10_funcarg_finalizer.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter12/12_10_funcarg_finalizer.pyc -------------------------------------------------------------------------------- /Chapter12/12_15_pytest_skipifmark.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter12/12_15_pytest_skipifmark.pyc -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Chapter12/12_13_pytest_simple_skip.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter12/12_13_pytest_simple_skip.pyc -------------------------------------------------------------------------------- /Chapter12/12_14_pytest_importorskip.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/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/casestudy/vigenere_cipher.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter12/casestudy/vigenere_cipher.pyc -------------------------------------------------------------------------------- /Chapter4/__pycache__/auth.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter4/__pycache__/auth.cpython-34.pyc -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/__pycache__/stats.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter12/__pycache__/stats.cpython-34.pyc -------------------------------------------------------------------------------- /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/__pycache__/timer.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter7/__pycache__/timer.cpython-34.pyc -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/casestudy/test_vigenere_cipher.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter12/casestudy/test_vigenere_cipher.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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Chapter12/__pycache__/test_mock.cpython-34-PYTEST.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter12/__pycache__/test_mock.cpython-34-PYTEST.pyc -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Chapter12/casestudy/__pycache__/vigenere_cipher.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter12/casestudy/__pycache__/vigenere_cipher.cpython-34.pyc -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Chapter12/casestudy/__pycache__/test_vigenere_cipher.cpython-34-PYTEST.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Second-Edition/HEAD/Chapter12/casestudy/__pycache__/test_vigenere_cipher.cpython-34-PYTEST.pyc -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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_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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Python 3 Object-oriented Programming - Second Edition 5 | 6 | Python 3 Object-oriented Programming - Second Edition 7 | 8 | This is the code repository for [Python 3 Object-oriented Programming - Second Edition ](https://prod.packtpub.com/in/application-development/python-3-object-oriented-programming-second-edition?utm_source=github&utm_medium=repository&utm_campaign=), published by Packt. 9 | 10 | **Building robust and maintainable software with object oriented design patterns in Python** 11 | 12 | ## What is this book about? 13 | 14 | This book covers the following exciting features: 15 | 16 | * Implement objects in Python by creating classes and defining methods 17 | * Separate related objects into a taxonomy of classes and describe the properties and behaviors of those objects via the class interface 18 | * Extend class functionality using inheritance 19 | * Understand when to use object-oriented features, and more importantly when not to use them 20 | * Discover what design patterns are and why they are different in Python 21 | 22 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1784398780) today! 23 | 24 | https://www.packtpub.com/ 26 | 27 | ## Instructions and Navigations 28 | 29 | **Following is what you need for this book:** 30 | 31 | This book specifically targets people who are new to object-oriented programming. It assumes you have basic Python skills. You'll learn object-oriented principles in depth. It is particularly useful for system administrator types who have used Python as a "glue" language and would like to improve their programming skills. 32 | If you are familiar with object-oriented programming in other languages, then this book will help you understand the idiomatic ways to apply your knowledge in the Python ecosystem 33 | 34 | With the following software and hardware list you can run all code files present in the book (Chapter 1-13). 35 | ### Software and Hardware List 36 | | Chapter | Software required | OS required | 37 | | -------- | ------------------------------------ | ----------------------------------- | 38 | | All | Python 3.4 | Windows, Mac OS X, and Linux (Any) | 39 | 40 | 41 | 42 | ## Get to Know the Author 43 | **Dusty Phillips** 44 | is a Canadian software developer and author currently living in New Brunswick. He has been active in the open source community for two decades and has been programming in Python for nearly as long. He holds a master's degree in computer science and has worked for Facebook, the United Nations, and several start-ups. He's currently researching privacy-preserving technology at beanstalk.network. 45 | 46 | 47 | ### Suggestions and Feedback 48 | [Click here](https://docs.google.com/forms/d/e/1FAIpQLSdy7dATC6QmEL81FIUuymZ0Wy9vH1jHkvpY57OiMeKGqib_Ow/viewform) if you have any feedback or suggestions. 49 | 50 | 51 | ### Download a free PDF 52 | 53 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
54 |

https://packt.link/free-ebook/9781784398781

--------------------------------------------------------------------------------