├── .gitignore ├── Chapter02 ├── Case Study_ Notebook │ ├── command_option.py │ ├── menu.py │ └── notebook.py ├── first_class.py ├── guarding.py ├── inner_class.py ├── name_mangling.py └── point.py ├── Chapter03 ├── Case Study_ Online Grader │ ├── example_runner.py │ ├── grader.py │ └── lessons.py ├── audio_player.py ├── contact.py ├── contact_multiple_inheritance.py ├── multiple_inheritance_bad_demo.py ├── multiple_inheritance_super.py └── odd_container.py ├── Chapter04 ├── Case Study_ Authorization │ ├── auth.py │ └── auth_driver.py ├── all_exception_syntax.py ├── even_integers.py ├── funny_division.py ├── inventory.py └── no_return.py ├── Chapter05 ├── Document.py ├── cached_webpage.py ├── colors.py ├── points.py ├── silly.py ├── zip_find_replace.py └── zip_processor.py ├── Chapter06 ├── Case Study_ Link Collector │ ├── link_collector.py │ └── sample_site │ │ ├── blog.html │ │ ├── case_study_serve │ │ └── meh.html │ │ ├── contact.html │ │ ├── esme.html │ │ ├── hobbies.html │ │ ├── index.html │ │ └── yoga.html ├── dataclass_stocks.py ├── letter_frequencies.py ├── song_sets.py ├── sorting_lists.py ├── tuple_stocks.py └── using_dictionaries.py ├── Chapter07 ├── Case Study_ Mailing Lists │ ├── addresses.db │ └── mailing_list.py ├── augmented_move.py ├── custom_sequence.py ├── default_options.py ├── enumerate_lines.py ├── event_timer.py └── passing_functions.py ├── Chapter08 ├── Case Study_ Templating Engine │ ├── context.json │ ├── done.html │ ├── footer.html │ ├── header.html │ ├── index.html │ ├── menu.html │ └── templater.py ├── email_formats.py ├── encoding_bytes.py ├── format_invoice.py ├── fun_with_regex.py ├── json_serializer.py ├── pickled_list ├── pickling.py ├── regex_check.py ├── sloc.py ├── string_formatting.py └── update_url.py ├── Chapter09 ├── Case Study_ Machine Learning │ ├── check_output.py │ ├── colors.csv │ ├── machine_learn.py │ ├── manual_classify.py │ ├── output.csv │ └── second_edition_machine_learn_with_coroutines.py ├── EXAMPLE_LOG.log ├── book_authors.py ├── capital_iterator.py ├── filesystem_generator.py ├── list_comp.py ├── list_comp_read_file.py ├── log_loop.py ├── sample_log.txt ├── score_tally.py ├── warning_generators.py └── xfs_error_log.py ├── Chapter10 ├── car_sales_template.py ├── inventory_observer.py ├── log_calls_decorator.py ├── singleton_state.py ├── socket_client.py ├── socket_decorator.py ├── tiling_strategy.py ├── xml_example.xml └── xml_state_parser.py ├── Chapter11 ├── age_calculator_adapter.py ├── car_flyweight.py ├── email_facade.py ├── folder_composite.py ├── formatter_factory.py ├── pythonic_window_command.py └── window_command.py ├── Chapter12 ├── Case Study_ Vigenère cipher │ ├── test_vigenere.py │ └── vigenere_cipher.py ├── average_raises.py ├── echo_server.py ├── first_unittest.py ├── flight_status_redis.py ├── stats.py ├── stats_list_setup.py ├── test_echo_server.py ├── test_flight_status.py ├── test_pytest_cleanup.py ├── test_pytest_setups.py ├── test_pytest_skipping.py ├── test_pytest_stats.py ├── test_skipping.py ├── test_statslist_setup.py └── test_with_pytest.py ├── Chapter13 ├── Case Study_ Compression │ ├── bricks.bmp │ ├── compiling1.bmp │ ├── compress_bmp.py │ ├── decompress_to_bmp.py │ ├── exploits_of_a_mom.bmp │ ├── python.bmp │ ├── results │ │ ├── bricks.bmp │ │ ├── bricks.rle │ │ ├── compiling1.rle │ │ ├── exploits_of_a_mom.rle │ │ ├── python.rle │ │ ├── row.rle │ │ └── sandwich.rle │ ├── row.bmp │ └── sandwich.bmp ├── async_sleep.py ├── basic_thread.py ├── dns_server.py ├── muchcpu.py ├── prime_factor.py ├── searching_with_futures.py ├── searching_with_queues.py ├── sort_client.py ├── sort_service.py └── weather_today.py ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .python-version 2 | .vscode 3 | __pycache__ 4 | lastfailed 5 | nodeids 6 | .pytest_cache 7 | .idea/* 8 | -------------------------------------------------------------------------------- /Chapter02/Case Study_ Notebook/command_option.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Third-Edition/6dc21b5e65465712c9923aabc9b713d327412258/Chapter02/Case Study_ Notebook/command_option.py -------------------------------------------------------------------------------- /Chapter02/Case Study_ Notebook/menu.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from notebook import Notebook 3 | 4 | 5 | class Menu: 6 | """Display a menu and respond to choices when run.""" 7 | 8 | def __init__(self): 9 | self.notebook = Notebook() 10 | self.choices = { 11 | "1": self.show_notes, 12 | "2": self.search_notes, 13 | "3": self.add_note, 14 | "4": self.modify_note, 15 | "5": self.quit, 16 | } 17 | 18 | def display_menu(self): 19 | print( 20 | """ 21 | Notebook Menu 22 | 23 | 1. Show all Notes 24 | 2. Search Notes 25 | 3. Add Note 26 | 4. Modify Note 27 | 5. Quit 28 | """ 29 | ) 30 | 31 | def run(self): 32 | """Display the menu and respond to choices.""" 33 | while True: 34 | self.display_menu() 35 | choice = input("Enter an option: ") 36 | action = self.choices.get(choice) 37 | if action: 38 | action() 39 | else: 40 | print("{0} is not a valid choice".format(choice)) 41 | 42 | def show_notes(self, notes=None): 43 | if not notes: 44 | notes = self.notebook.notes 45 | for note in notes: 46 | print("{0}: {1}\n{2}".format(note.id, note.tags, note.memo)) 47 | 48 | def search_notes(self): 49 | filter = input("Search for: ") 50 | notes = self.notebook.search(filter) 51 | self.show_notes(notes) 52 | 53 | def add_note(self): 54 | memo = input("Enter a memo: ") 55 | self.notebook.new_note(memo) 56 | print("Your note has been added.") 57 | 58 | def modify_note(self): 59 | id = input("Enter a note id: ") 60 | memo = input("Enter a memo: ") 61 | tags = input("Enter tags: ") 62 | if memo: 63 | self.notebook.modify_memo(id, memo) 64 | if tags: 65 | self.notebook.modify_tags(id, tags) 66 | 67 | def quit(self): 68 | print("Thank you for using your notebook today.") 69 | sys.exit(0) 70 | 71 | 72 | if __name__ == "__main__": 73 | Menu().run() 74 | -------------------------------------------------------------------------------- /Chapter02/Case Study_ Notebook/notebook.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | # Store the next available id for all new notes 4 | last_id = 0 5 | 6 | 7 | class Note: 8 | """Represent a note in the notebook. Match against a 9 | string in searches and store tags for each note.""" 10 | 11 | def __init__(self, memo, tags=""): 12 | """initialize a note with memo and optional 13 | space-separated tags. Automatically set the note's 14 | creation date and a unique id.""" 15 | self.memo = memo 16 | self.tags = tags 17 | self.creation_date = datetime.date.today() 18 | global last_id 19 | last_id += 1 20 | self.id = last_id 21 | 22 | def match(self, filter): 23 | """Determine if this note matches the filter 24 | text. Return True if it matches, False otherwise. 25 | 26 | Search is case sensitive and matches both text and 27 | tags.""" 28 | return filter in self.memo or filter in self.tags 29 | 30 | 31 | class Notebook: 32 | """Represent a collection of notes that can be tagged, 33 | modified, and searched.""" 34 | 35 | def __init__(self): 36 | """Initialize a notebook with an empty list.""" 37 | self.notes = [] 38 | 39 | def new_note(self, memo, tags=""): 40 | """Create a new note and add it to the list.""" 41 | self.notes.append(Note(memo, tags)) 42 | 43 | def _find_note(self, note_id): 44 | """Locate the note with the given id.""" 45 | for note in self.notes: 46 | if str(note.id) == str(note_id): 47 | return note 48 | return None 49 | 50 | def modify_memo(self, note_id, memo): 51 | """Find the note with the given id and change its 52 | memo to the given value.""" 53 | note = self._find_note(note_id) 54 | if note: 55 | note.memo = memo 56 | return True 57 | return False 58 | 59 | def modify_tags(self, note_id, tags): 60 | """Find the note with the given id and change its 61 | tags to the given value.""" 62 | note = self._find_note(note_id) 63 | if note: 64 | note.tags = tags 65 | return True 66 | return False 67 | 68 | def search(self, filter): 69 | """Find all notes that match the given filter 70 | string.""" 71 | return [note for note in self.notes if note.match(filter)] 72 | -------------------------------------------------------------------------------- /Chapter02/first_class.py: -------------------------------------------------------------------------------- 1 | class MyFirstClass: 2 | pass -------------------------------------------------------------------------------- /Chapter02/guarding.py: -------------------------------------------------------------------------------- 1 | class UsefulClass: 2 | """This class might be useful to other modules.""" 3 | 4 | pass 5 | 6 | 7 | def main(): 8 | """Creates a useful class and does something with it for our module.""" 9 | useful = UsefulClass() 10 | print(useful) 11 | 12 | 13 | if __name__ == "__main__": 14 | main() 15 | 16 | -------------------------------------------------------------------------------- /Chapter02/inner_class.py: -------------------------------------------------------------------------------- 1 | def format_string(string, formatter=None): 2 | """Format a string using the formatter object, which 3 | is expected to have a format() method that accepts 4 | a string.""" 5 | 6 | class DefaultFormatter: 7 | """Format a string in title case.""" 8 | 9 | def format(self, string): 10 | return str(string).title() 11 | 12 | if not formatter: 13 | formatter = DefaultFormatter() 14 | 15 | return formatter.format(string) 16 | 17 | 18 | hello_string = "hello world, how are you today?" 19 | print(" input: " + hello_string) 20 | print("output: " + format_string(hello_string)) 21 | -------------------------------------------------------------------------------- /Chapter02/name_mangling.py: -------------------------------------------------------------------------------- 1 | class SecretString: 2 | """A not-at-all secure way to store a secret string.""" 3 | 4 | def __init__(self, plain_string, pass_phrase): 5 | self.__plain_string = plain_string 6 | self.__pass_phrase = pass_phrase 7 | 8 | def decrypt(self, pass_phrase): 9 | """Only show the string if the pass_phrase is correct.""" 10 | if pass_phrase == self.__pass_phrase: 11 | return self.__plain_string 12 | else: 13 | return "" 14 | -------------------------------------------------------------------------------- /Chapter02/point.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | 4 | class Point: 5 | "Represents a point in two-dimensional geometric coordinates" 6 | 7 | def __init__(self, x=0, y=0): 8 | """Initialize the position of a new point. The x and y 9 | coordinates can be specified. If they are not, the 10 | point defaults to the origin.""" 11 | self.move(x, y) 12 | 13 | def move(self, x, y): 14 | "Move the point to a new location in 2D space." 15 | self.x = x 16 | self.y = y 17 | 18 | def reset(self): 19 | "Reset the point back to the geometric origin: 0, 0" 20 | self.move(0, 0) 21 | 22 | def calculate_distance(self, other_point): 23 | """Calculate the distance from this point to a second 24 | point passed as a parameter. 25 | 26 | This function uses the Pythagorean Theorem to calculate 27 | the distance between the two points. The distance is 28 | returned as a float.""" 29 | 30 | return math.sqrt( 31 | (self.x - other_point.x) ** 2 32 | + (self.y - other_point.y) ** 2 33 | ) 34 | 35 | 36 | # how to use it: 37 | point1 = Point() 38 | point2 = Point() 39 | 40 | point1.reset() 41 | point2.move(5, 0) 42 | print(point2.calculate_distance(point1)) 43 | assert point2.calculate_distance(point1) == point1.calculate_distance( 44 | point2 45 | ) 46 | point1.move(3, 4) 47 | print(point1.calculate_distance(point2)) 48 | print(point1.calculate_distance(point1)) 49 | -------------------------------------------------------------------------------- /Chapter03/Case Study_ Online Grader/example_runner.py: -------------------------------------------------------------------------------- 1 | from grader import Grader 2 | from lessons import IntroToPython, Statistics 3 | 4 | grader = Grader() 5 | itp_id = grader.register(IntroToPython) 6 | stat_id = grader.register(Statistics) 7 | 8 | grader.start_assignment("Tammy", itp_id) 9 | print("Tammy's Lesson:", grader.get_lesson("Tammy")) 10 | print( 11 | "Tammy's check:", 12 | grader.check_assignment("Tammy", "a = 1 ; b = 'hello'"), 13 | ) 14 | print( 15 | "Tammy's other check:", 16 | grader.check_assignment("Tammy", "a = 1\nb = 'hello'"), 17 | ) 18 | 19 | print(grader.assignment_summary("Tammy")) 20 | 21 | grader.start_assignment("Tammy", stat_id) 22 | print("Tammy's Lesson:", grader.get_lesson("Tammy")) 23 | print("Tammy's check:", grader.check_assignment("Tammy", "avg=5.25")) 24 | print( 25 | "Tammy's other check:", 26 | grader.check_assignment( 27 | "Tammy", "avg = statistics.mean([1, 5, 18, -3])" 28 | ), 29 | ) 30 | 31 | print(grader.assignment_summary("Tammy")) 32 | -------------------------------------------------------------------------------- /Chapter03/Case Study_ Online Grader/grader.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import uuid 3 | 4 | 5 | class Grader: 6 | def __init__(self): 7 | self.student_graders = {} 8 | self.assignment_classes = {} 9 | 10 | def register(self, assignment_class): 11 | if not issubclass(assignment_class, Assignment): 12 | raise RuntimeError( 13 | "Your class does not have the right methods" 14 | ) 15 | 16 | id = uuid.uuid4() 17 | self.assignment_classes[id] = assignment_class 18 | return id 19 | 20 | def start_assignment(self, student, id): 21 | self.student_graders[student] = AssignmentGrader( 22 | student, self.assignment_classes[id] 23 | ) 24 | 25 | def get_lesson(self, student): 26 | assignment = self.student_graders[student] 27 | return assignment.lesson() 28 | 29 | def check_assignment(self, student, code): 30 | assignment = self.student_graders[student] 31 | return assignment.check(code) 32 | 33 | def assignment_summary(self, student): 34 | grader = self.student_graders[student] 35 | return f""" 36 | {student}'s attempts at {grader.assignment.__class__.__name__}: 37 | 38 | attempts: {grader.attempts} 39 | correct: {grader.correct_attempts} 40 | 41 | passed: {grader.correct_attempts > 0} 42 | """ 43 | 44 | 45 | class Assignment(metaclass=abc.ABCMeta): 46 | @abc.abstractmethod 47 | def lesson(self, student): 48 | pass 49 | 50 | @abc.abstractmethod 51 | def check(self, code): 52 | pass 53 | 54 | @classmethod 55 | def __subclasshook__(cls, C): 56 | if cls is Assignment: 57 | attrs = set(dir(C)) 58 | if set(cls.__abstractmethods__) <= attrs: 59 | return True 60 | 61 | return NotImplemented 62 | 63 | 64 | class AssignmentGrader: 65 | def __init__(self, student, AssignmentClass): 66 | self.assignment = AssignmentClass() 67 | self.assignment.student = student 68 | self.attempts = 0 69 | self.correct_attempts = 0 70 | 71 | def check(self, code): 72 | self.attempts += 1 73 | result = self.assignment.check(code) 74 | if result: 75 | self.correct_attempts += 1 76 | 77 | return result 78 | 79 | def lesson(self): 80 | return self.assignment.lesson() 81 | -------------------------------------------------------------------------------- /Chapter03/Case Study_ Online Grader/lessons.py: -------------------------------------------------------------------------------- 1 | from grader import Assignment 2 | 3 | 4 | class IntroToPython: 5 | def lesson(self): 6 | return f""" 7 | Hello {self.student}. define two variables, 8 | an integer named a with value 1 9 | and a string named b with value 'hello' 10 | 11 | """ 12 | 13 | def check(self, code): 14 | return code == "a = 1\nb = 'hello'" 15 | 16 | 17 | class Statistics(Assignment): 18 | def lesson(self): 19 | return ( 20 | "Good work so far, " 21 | + self.student 22 | + ". Now calculate the average of the numbers " 23 | + " 1, 5, 18, -3 and assign to a variable named 'avg'" 24 | ) 25 | 26 | def check(self, code): 27 | import statistics 28 | 29 | code = "import statistics\n" + code 30 | 31 | local_vars = {} 32 | global_vars = {} 33 | exec(code, global_vars, local_vars) 34 | 35 | return local_vars.get("avg") == statistics.mean([1, 5, 18, -3]) 36 | 37 | 38 | # Some code to test that it's doing the right thing 39 | print( 40 | "IntroToPython is Assignment subclass:", 41 | issubclass(IntroToPython, Assignment), 42 | ) 43 | print( 44 | "Statistics is Assignment subclass:", 45 | issubclass(Statistics, Assignment), 46 | ) 47 | 48 | -------------------------------------------------------------------------------- /Chapter03/audio_player.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | 4 | class AudioFile: 5 | def __init__(self, filename): 6 | if not filename.endswith(self.ext): 7 | raise Exception("Invalid file format") 8 | 9 | self.filename = filename 10 | 11 | 12 | class MP3File(AudioFile): 13 | ext = "mp3" 14 | 15 | def play(self): 16 | print("playing {} as mp3".format(self.filename)) 17 | 18 | 19 | class WavFile(AudioFile): 20 | ext = "wav" 21 | 22 | def play(self): 23 | print("playing {} as wav".format(self.filename)) 24 | 25 | 26 | class OggFile(AudioFile): 27 | ext = "ogg" 28 | 29 | def play(self): 30 | print("playing {} as ogg".format(self.filename)) 31 | 32 | 33 | class FlacFile: 34 | def __init__(self, filename): 35 | if not filename.endswith(".flac"): 36 | raise Exception("Invalid file format") 37 | 38 | self.filename = filename 39 | 40 | def play(self): 41 | print("playing {} as flac".format(self.filename)) 42 | 43 | 44 | class MediaLoader(metaclass=abc.ABCMeta): 45 | @abc.abstractmethod 46 | def play(self): 47 | pass 48 | 49 | @property 50 | @abc.abstractmethod 51 | def ext(self): 52 | pass 53 | 54 | @classmethod 55 | def __subclasshook__(cls, C): 56 | if cls is MediaLoader: 57 | attrs = set(dir(C)) 58 | if set(cls.__abstractmethods__) <= attrs: 59 | return True 60 | 61 | return NotImplemented 62 | -------------------------------------------------------------------------------- /Chapter03/contact.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 | 12 | class Contact: 13 | all_contacts = ContactList() 14 | 15 | def __init__(self, name, email): 16 | self.name = name 17 | self.email = email 18 | Contact.all_contacts.append(self) 19 | 20 | 21 | class Supplier(Contact): 22 | def order(self, order): 23 | print( 24 | "If this were a real system we would send " 25 | "'{}' order to '{}'".format(order, self.name) 26 | ) 27 | 28 | 29 | class Friend(Contact): 30 | def __init__(self, name, email, phone): 31 | self.name = name 32 | self.email = email 33 | self.phone = phone 34 | 35 | 36 | class MailSender: 37 | def send_mail(self, message): 38 | print("Sending mail to " + self.email) 39 | # Add e-mail logic here 40 | 41 | 42 | class EmailableContact(Contact, MailSender): 43 | pass 44 | -------------------------------------------------------------------------------- /Chapter03/contact_multiple_inheritance.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 | 11 | class AddressHolder: 12 | def __init__(self, street="", city="", state="", code="", **kwargs): 13 | super().__init__(**kwargs) 14 | self.street = street 15 | self.city = city 16 | self.state = state 17 | self.code = code 18 | 19 | 20 | class Friend(Contact, AddressHolder): 21 | def __init__(self, phone="", **kwargs): 22 | super().__init__(**kwargs) 23 | self.phone = phone 24 | -------------------------------------------------------------------------------- /Chapter03/multiple_inheritance_bad_demo.py: -------------------------------------------------------------------------------- 1 | class BaseClass: 2 | num_base_calls = 0 3 | 4 | def call_me(self): 5 | print("Calling method on Base Class") 6 | self.num_base_calls += 1 7 | 8 | 9 | class LeftSubclass(BaseClass): 10 | num_left_calls = 0 11 | 12 | def call_me(self): 13 | BaseClass.call_me(self) 14 | print("Calling method on Left Subclass") 15 | self.num_left_calls += 1 16 | 17 | 18 | class RightSubclass(BaseClass): 19 | num_right_calls = 0 20 | 21 | def call_me(self): 22 | BaseClass.call_me(self) 23 | print("Calling method on Right Subclass") 24 | self.num_right_calls += 1 25 | 26 | 27 | class Subclass(LeftSubclass, RightSubclass): 28 | num_sub_calls = 0 29 | 30 | def call_me(self): 31 | LeftSubclass.call_me(self) 32 | RightSubclass.call_me(self) 33 | print("Calling method on Subclass") 34 | self.num_sub_calls += 1 35 | -------------------------------------------------------------------------------- /Chapter03/multiple_inheritance_super.py: -------------------------------------------------------------------------------- 1 | class BaseClass: 2 | num_base_calls = 0 3 | 4 | def call_me(self): 5 | print("Calling method on Base Class") 6 | self.num_base_calls += 1 7 | 8 | 9 | class LeftSubclass(BaseClass): 10 | num_left_calls = 0 11 | 12 | def call_me(self): 13 | super().call_me() 14 | print("Calling method on Left Subclass") 15 | self.num_left_calls += 1 16 | 17 | 18 | class RightSubclass(BaseClass): 19 | num_right_calls = 0 20 | 21 | def call_me(self): 22 | super().call_me() 23 | print("Calling method on Right Subclass") 24 | self.num_right_calls += 1 25 | 26 | 27 | class Subclass(LeftSubclass, RightSubclass): 28 | num_sub_calls = 0 29 | 30 | def call_me(self): 31 | super().call_me() 32 | print("Calling method on Subclass") 33 | self.num_sub_calls += 1 34 | -------------------------------------------------------------------------------- /Chapter03/odd_container.py: -------------------------------------------------------------------------------- 1 | class OddContainer: 2 | def __contains__(self, x): 3 | if not isinstance(x, int) or not x % 2: 4 | return False 5 | return True 6 | -------------------------------------------------------------------------------- /Chapter04/Case Study_ Authorization/auth.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | 4 | class AuthException(Exception): 5 | def __init__(self, username, user=None): 6 | super().__init__(username, user) 7 | self.username = username 8 | self.user = user 9 | 10 | 11 | class UsernameAlreadyExists(AuthException): 12 | pass 13 | 14 | 15 | class PasswordTooShort(AuthException): 16 | pass 17 | 18 | 19 | class InvalidUsername(AuthException): 20 | pass 21 | 22 | 23 | class InvalidPassword(AuthException): 24 | pass 25 | 26 | 27 | class PermissionError(Exception): 28 | pass 29 | 30 | 31 | class NotLoggedInError(AuthException): 32 | pass 33 | 34 | 35 | class NotPermittedError(AuthException): 36 | pass 37 | 38 | 39 | class User: 40 | def __init__(self, username, password): 41 | """Create a new user object. The password 42 | will be encrypted before storing.""" 43 | self.username = username 44 | self.password = self._encrypt_pw(password) 45 | self.is_logged_in = False 46 | 47 | def _encrypt_pw(self, password): 48 | """Encrypt the password with the username and return 49 | the sha digest.""" 50 | hash_string = self.username + password 51 | hash_string = hash_string.encode("utf8") 52 | return hashlib.sha256(hash_string).hexdigest() 53 | 54 | def check_password(self, password): 55 | """Return True if the password is valid for this 56 | user, false otherwise.""" 57 | encrypted = self._encrypt_pw(password) 58 | return encrypted == self.password 59 | 60 | 61 | class Authenticator: 62 | def __init__(self): 63 | """Construct an authenticator to manage 64 | users logging in and out.""" 65 | self.users = {} 66 | 67 | def add_user(self, username, password): 68 | if username in self.users: 69 | raise UsernameAlreadyExists(username) 70 | if len(password) < 6: 71 | raise PasswordTooShort(username) 72 | self.users[username] = User(username, password) 73 | 74 | def login(self, username, password): 75 | try: 76 | user = self.users[username] 77 | except KeyError: 78 | raise InvalidUsername(username) 79 | 80 | if not user.check_password(password): 81 | raise InvalidPassword(username, user) 82 | 83 | user.is_logged_in = True 84 | return True 85 | 86 | def is_logged_in(self, username): 87 | if username in self.users: 88 | return self.users[username].is_logged_in 89 | return False 90 | 91 | 92 | class Authorizor: 93 | def __init__(self, authenticator): 94 | self.authenticator = authenticator 95 | self.permissions = {} 96 | 97 | def add_permission(self, perm_name): 98 | """Create a new permission that users 99 | can be added to""" 100 | try: 101 | perm_set = self.permissions[perm_name] 102 | except KeyError: 103 | self.permissions[perm_name] = set() 104 | else: 105 | raise PermissionError("Permission Exists") 106 | 107 | def permit_user(self, perm_name, username): 108 | """Grant the given permission to the user""" 109 | try: 110 | perm_set = self.permissions[perm_name] 111 | except KeyError: 112 | raise PermissionError("Permission does not exist") 113 | else: 114 | if username not in self.authenticator.users: 115 | raise InvalidUsername(username) 116 | perm_set.add(username) 117 | 118 | def check_permission(self, perm_name, username): 119 | if not self.authenticator.is_logged_in(username): 120 | raise NotLoggedInError(username) 121 | try: 122 | perm_set = self.permissions[perm_name] 123 | except KeyError: 124 | raise PermissionError("Permission does not exist") 125 | else: 126 | if username not in perm_set: 127 | raise NotPermittedError(username) 128 | else: 129 | return True 130 | 131 | 132 | authenticator = Authenticator() 133 | 134 | authorizor = Authorizor(authenticator) 135 | -------------------------------------------------------------------------------- /Chapter04/Case Study_ Authorization/auth_driver.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 | 10 | class Editor: 11 | def __init__(self): 12 | self.username = None 13 | self.menu_map = { 14 | "login": self.login, 15 | "test": self.test, 16 | "change": self.change, 17 | "quit": self.quit, 18 | } 19 | 20 | def login(self): 21 | logged_in = False 22 | while not logged_in: 23 | username = input("username: ") 24 | password = input("password: ") 25 | try: 26 | logged_in = auth.authenticator.login(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(permission, self.username) 37 | except auth.NotLoggedInError as e: 38 | print("{} is not logged in".format(e.username)) 39 | return False 40 | except auth.NotPermittedError as e: 41 | print("{} cannot {}".format(e.username, permission)) 42 | return False 43 | else: 44 | return True 45 | 46 | def test(self): 47 | if self.is_permitted("test program"): 48 | print("Testing program now...") 49 | 50 | def change(self): 51 | if self.is_permitted("change program"): 52 | print("Changing program now...") 53 | 54 | def quit(self): 55 | raise SystemExit() 56 | 57 | def menu(self): 58 | try: 59 | answer = "" 60 | while True: 61 | print( 62 | """ 63 | Please enter a command: 64 | \tlogin\tLogin 65 | \ttest\tTest the program 66 | \tchange\tChange the program 67 | \tquit\tQuit 68 | """ 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(answer)) 75 | else: 76 | func() 77 | finally: 78 | print("Thank you for testing the auth module") 79 | 80 | 81 | Editor().menu() 82 | -------------------------------------------------------------------------------- /Chapter04/all_exception_syntax.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | some_exceptions = [ValueError, TypeError, IndexError, None] 4 | 5 | try: 6 | choice = random.choice(some_exceptions) 7 | print("raising {}".format(choice)) 8 | if choice: 9 | raise choice("An error") 10 | except ValueError: 11 | print("Caught a ValueError") 12 | except TypeError: 13 | print("Caught a TypeError") 14 | except Exception as e: 15 | print("Caught some other error: %s" % (e.__class__.__name__)) 16 | else: 17 | print("This code called if there is no exception") 18 | finally: 19 | print("This cleanup code is always called") 20 | -------------------------------------------------------------------------------- /Chapter04/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 | -------------------------------------------------------------------------------- /Chapter04/funny_division.py: -------------------------------------------------------------------------------- 1 | def funny_division(divider): 2 | try: 3 | return 100 / divider 4 | except ZeroDivisionError: 5 | return "Zero is not a good idea!" 6 | 7 | 8 | print(funny_division(0)) 9 | print(funny_division(50.0)) 10 | print(funny_division("hello")) 11 | 12 | 13 | def funny_division2(divider): 14 | try: 15 | if divider == 13: 16 | raise ValueError("13 is an unlucky number") 17 | return 100 / divider 18 | except (ZeroDivisionError, TypeError): 19 | return "Enter a number other than zero" 20 | 21 | 22 | for val in (0, "hello", 50.0, 13): 23 | 24 | print("Testing {}:".format(val), end=" ") 25 | print(funny_division2(val)) 26 | 27 | 28 | def funny_division3(divider): 29 | try: 30 | if divider == 13: 31 | raise ValueError("13 is an unlucky number") 32 | return 100 / divider 33 | except ZeroDivisionError: 34 | return "Enter a number other than zero" 35 | except TypeError: 36 | return "Enter a numerical value" 37 | except ValueError: 38 | print("No, No, not 13!") 39 | raise 40 | -------------------------------------------------------------------------------- /Chapter04/inventory.py: -------------------------------------------------------------------------------- 1 | class OutOfStock(Exception): 2 | pass 3 | 4 | 5 | class InvalidItemType(Exception): 6 | pass 7 | 8 | 9 | class Inventory: 10 | def lock(self, item_type): 11 | """Select the type of item that is going to 12 | be manipulated. This method will lock the 13 | item so nobody else can manipulate the 14 | inventory until it's returned. This prevents 15 | selling the same item to two different 16 | customers.""" 17 | pass 18 | 19 | def unlock(self, item_type): 20 | """Release the given type so that other 21 | customers can access it.""" 22 | pass 23 | 24 | def purchase(self, item_type): 25 | """If the item is not locked, raise an 26 | exception. If the item_type does not exist, 27 | raise an exception. If the item is currently 28 | out of stock, raise an exception. If the item 29 | is available, subtract one item and return 30 | the number of items left.""" 31 | pass 32 | 33 | 34 | item_type = "widget" 35 | inv = Inventory() 36 | inv.lock(item_type) 37 | try: 38 | num_left = inv.purchase(item_type) 39 | except InvalidItemType: 40 | print("Sorry, we don't sell {}".format(item_type)) 41 | except OutOfStock: 42 | print("Sorry, that item is out of stock.") 43 | else: 44 | print( 45 | "Purchase complete. There are " 46 | "{} {}s left".format(num_left, item_type) 47 | ) 48 | finally: 49 | inv.unlock(item_type) 50 | -------------------------------------------------------------------------------- /Chapter04/no_return.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 | 8 | def call_exceptor(): 9 | print("call_exceptor starts here...") 10 | no_return() 11 | print("an exception was raised...") 12 | print("...so these lines don't run") 13 | 14 | 15 | try: 16 | no_return() 17 | except: 18 | print("I caught an exception") 19 | print("executed after the exception") 20 | -------------------------------------------------------------------------------- /Chapter05/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, character) 11 | self.cursor.forward() 12 | 13 | def delete(self): 14 | del self.characters[self.cursor.position] 15 | 16 | def save(self): 17 | with open(self.filename, "w") as f: 18 | f.write("".join(self.characters)) 19 | 20 | @property 21 | def string(self): 22 | return "".join((str(c) for c in self.characters)) 23 | 24 | 25 | class Cursor: 26 | def __init__(self, document): 27 | self.document = document 28 | self.position = 0 29 | 30 | def forward(self): 31 | self.position += 1 32 | 33 | def back(self): 34 | self.position -= 1 35 | 36 | def home(self): 37 | while self.document.characters[self.position - 1].character != "\n": 38 | self.position -= 1 39 | if self.position == 0: 40 | # Got to beginning of file before newline 41 | break 42 | 43 | def end(self): 44 | while ( 45 | self.position < len(self.document.characters) 46 | and self.document.characters[self.position].character != "\n" 47 | ): 48 | self.position += 1 49 | 50 | 51 | class Character: 52 | def __init__(self, character, bold=False, italic=False, underline=False): 53 | assert len(character) == 1 54 | self.character = character 55 | self.bold = bold 56 | self.italic = italic 57 | self.underline = underline 58 | 59 | def __str__(self): 60 | bold = "*" if self.bold else "" 61 | italic = "/" if self.italic else "" 62 | underline = "_" if self.underline else "" 63 | return bold + italic + underline + self.character 64 | -------------------------------------------------------------------------------- /Chapter05/cached_webpage.py: -------------------------------------------------------------------------------- 1 | from urllib.request import urlopen 2 | 3 | 4 | class WebPage: 5 | def __init__(self, url): 6 | self.url = url 7 | self._content = None 8 | 9 | @property 10 | def content(self): 11 | if not self._content: 12 | print("Retrieving New Page...") 13 | self._content = urlopen(self.url).read() 14 | return self._content 15 | -------------------------------------------------------------------------------- /Chapter05/colors.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 | -------------------------------------------------------------------------------- /Chapter05/points.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 | 13 | 14 | square = [(1,1), (1,2), (2,2), (2,1)] 15 | perimeter(square) 16 | 17 | 18 | class Point: 19 | def __init__(self, x, y): 20 | self.x = x 21 | self.y = y 22 | 23 | def distance(self, p2): 24 | return math.sqrt((self.x-p2.x)**2 + (self.y-p2.y)**2) 25 | 26 | class Polygon: 27 | def __init__(self): 28 | self.vertices = [] 29 | 30 | def add_point(self, point): 31 | self.vertices.append((point)) 32 | 33 | def perimeter(self): 34 | perimeter = 0 35 | points = self.vertices + [self.vertices[0]] 36 | for i in range(len(self.vertices)): 37 | perimeter += points[i].distance(points[i+1]) 38 | return perimeter 39 | 40 | 41 | square = Polygon() 42 | square.add_point(Point(1,1)) 43 | square.add_point(Point(1,2)) 44 | square.add_point(Point(2,2)) 45 | square.add_point(Point(2,1)) 46 | square.perimeter() 47 | 48 | class Polygon2(Polygon): 49 | def __init__(self, points=None): 50 | points = points if points else [] 51 | self.vertices = [] 52 | for point in points: 53 | if isinstance(point, tuple): 54 | point = Point(*point) 55 | self.vertices.append(point) 56 | -------------------------------------------------------------------------------- /Chapter05/silly.py: -------------------------------------------------------------------------------- 1 | class Silly: 2 | def _get_silly(self): 3 | print("You are getting silly") 4 | return self._silly 5 | 6 | def _set_silly(self, value): 7 | print("You are making silly {}".format(value)) 8 | self._silly = value 9 | 10 | def _del_silly(self): 11 | print("Whoah, you killed silly!") 12 | del self._silly 13 | 14 | silly = property(_get_silly, _set_silly, _del_silly, "This is a silly property") 15 | 16 | 17 | class SillyDecorated: 18 | @property 19 | def silly(self): 20 | "This is a silly property" 21 | print("You are getting silly") 22 | return self._silly 23 | 24 | @silly.setter 25 | def silly(self, value): 26 | print("You are making silly {}".format(value)) 27 | self._silly = value 28 | 29 | @silly.deleter 30 | def silly(self): 31 | print("Whoah, you killed silly!") 32 | del self._silly 33 | -------------------------------------------------------------------------------- /Chapter05/zip_find_replace.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import shutil 3 | import zipfile 4 | from pathlib import Path 5 | 6 | 7 | class ZipReplace: 8 | def __init__(self, filename, search_string, replace_string): 9 | self.filename = filename 10 | self.search_string = search_string 11 | self.replace_string = replace_string 12 | self.temp_directory = Path(f"unzipped-{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(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(self.search_string, self.replace_string) 29 | with filename.open("w") as file: 30 | file.write(contents) 31 | 32 | def zip_files(self): 33 | with zipfile.ZipFile(self.filename, "w") as file: 34 | for filename in self.temp_directory.iterdir(): 35 | file.write(filename, filename.name) 36 | shutil.rmtree(self.temp_directory) 37 | 38 | 39 | if __name__ == "__main__": 40 | ZipReplace(*sys.argv[1:4]).zip_find_replace() 41 | -------------------------------------------------------------------------------- /Chapter05/zip_processor.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import shutil 3 | import zipfile 4 | from pathlib import Path 5 | from PIL import Image 6 | 7 | 8 | class ZipProcessor: 9 | def __init__(self, zipname): 10 | self.zipname = zipname 11 | self.temp_directory = Path(f"unzipped-{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(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(filename, filename.name) 27 | shutil.rmtree(self.temp_directory) 28 | 29 | 30 | class ZipReplace(ZipProcessor): 31 | def __init__(self, filename, search_string, replace_string): 32 | super().__init__(filename) 33 | self.search_string = search_string 34 | self.replace_string = replace_string 35 | 36 | def process_files(self): 37 | """perform a search and replace on all files in the 38 | temporary directory""" 39 | for filename in self.temp_directory.iterdir(): 40 | with filename.open() as file: 41 | contents = file.read() 42 | contents = contents.replace(self.search_string, self.replace_string) 43 | with filename.open("w") as file: 44 | file.write(contents) 45 | 46 | 47 | class ScaleZip(ZipProcessor): 48 | def process_files(self): 49 | """Scale each image in the directory to 640x480""" 50 | for filename in self.temp_directory.iterdir(): 51 | im = Image.open(str(filename)) 52 | scaled = im.resize((640, 480)) 53 | scaled.save(filename) 54 | 55 | 56 | if __name__ == "__main__": 57 | # ZipReplace(*sys.argv[1:4]).process_zip() 58 | ScaleZip(*sys.argv[1:4]).process_zip() 59 | -------------------------------------------------------------------------------- /Chapter06/Case Study_ Link Collector/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 | 7 | LINK_REGEX = re.compile("]*href=['\"]([^'\"]+)['\"][^>]*>") 8 | 9 | 10 | class LinkCollector: 11 | def __init__(self, url): 12 | self.url = "http://%s" % urlparse(url).netloc 13 | self.collected_links = {} 14 | self.visited_links = set() 15 | 16 | def collect_links(self): 17 | queue = Queue() 18 | queue.put(self.url) 19 | while not queue.empty(): 20 | url = queue.get().rstrip("/") 21 | self.visited_links.add(url) 22 | page = str(urlopen(url).read()) 23 | links = LINK_REGEX.findall(page) 24 | links = { 25 | self.normalize_url(urlparse(url).path, link) 26 | for link in links 27 | } 28 | self.collected_links[url] = links 29 | for link in links: 30 | self.collected_links.setdefault(link, set()) 31 | unvisited_links = links.difference(self.visited_links) 32 | for link in unvisited_links: 33 | if link.startswith(self.url): 34 | queue.put(link) 35 | 36 | def normalize_url(self, path, link): 37 | if link.startswith("http://"): 38 | return link.rstrip("/") 39 | elif link.startswith("/"): 40 | return self.url + link.rstrip("/") 41 | else: 42 | return ( 43 | self.url 44 | + path.rpartition("/")[0] 45 | + "/" 46 | + link.rstrip("/") 47 | ) 48 | 49 | 50 | if __name__ == "__main__": 51 | collector = LinkCollector(sys.argv[1]) 52 | collector.collect_links() 53 | for link, item in collector.collected_links.items(): 54 | print("%s: %s" % (link, item)) 55 | -------------------------------------------------------------------------------- /Chapter06/Case Study_ Link Collector/sample_site/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 | -------------------------------------------------------------------------------- /Chapter06/Case Study_ Link Collector/sample_site/case_study_serve/meh.html: -------------------------------------------------------------------------------- 1 | 2 | Meh 3 | 4 | -------------------------------------------------------------------------------- /Chapter06/Case Study_ Link Collector/sample_site/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 | -------------------------------------------------------------------------------- /Chapter06/Case Study_ Link Collector/sample_site/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 | -------------------------------------------------------------------------------- /Chapter06/Case Study_ Link Collector/sample_site/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 | -------------------------------------------------------------------------------- /Chapter06/Case Study_ Link Collector/sample_site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Contact us 4 | Blog 5 | My Dog 6 | Some hobbies 7 | Contact AGAIN 8 | Favourite OS 9 | 10 | 11 | -------------------------------------------------------------------------------- /Chapter06/Case Study_ Link Collector/sample_site/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 | Meh 6 | 7 | 8 | -------------------------------------------------------------------------------- /Chapter06/dataclass_stocks.py: -------------------------------------------------------------------------------- 1 | from dataclasses import make_dataclass, dataclass 2 | 3 | # using make_dataclass 4 | Stock = make_dataclass("Stock", ["symbol", "current", "high", "low"]) 5 | stock = Stock("FB", 177.46, high=178.67, low=175.79) 6 | 7 | 8 | # compared to regular object 9 | class StockRegClass: 10 | def __init__(self, name, current, high, low): 11 | self.name = name 12 | self.current = current 13 | self.high = high 14 | self.low = low 15 | 16 | 17 | stock_reg_class = StockRegClass("FB", 177.46, high=178.67, low=175.79) 18 | 19 | 20 | # using dataclass decorator 21 | @dataclass 22 | class StockDecorated: 23 | name: str 24 | current: float 25 | high: float 26 | low: float 27 | 28 | 29 | stock_decorated = StockDecorated("FB", 177.46, high=178.67, low=175.79) 30 | 31 | 32 | @dataclass 33 | class StockDefaults: 34 | name: str 35 | current: float = 0.0 36 | high: float = 0.0 37 | low: float = 0.0 38 | 39 | 40 | stock_defaults = StockDefaults("FB") 41 | 42 | 43 | @dataclass(order=True) 44 | class StockOrdered: 45 | name: str 46 | current: float = 0.0 47 | high: float = 0.0 48 | low: float = 0.0 49 | 50 | 51 | stock_ordered1 = StockOrdered("FB", 177.46, high=178.67, low=175.79) 52 | stock_ordered2 = StockOrdered("FB") 53 | stock_ordered3 = StockOrdered("FB", 178.42, high=179.28, low=176.39) 54 | -------------------------------------------------------------------------------- /Chapter06/letter_frequencies.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | from collections import defaultdict 3 | import string 4 | 5 | 6 | def letter_frequency(sentence): 7 | frequencies = defaultdict(int) 8 | for letter in sentence: 9 | frequencies[letter] += 1 10 | return frequencies 11 | 12 | 13 | num_items = 0 14 | 15 | 16 | def tuple_counter(): 17 | global num_items 18 | num_items += 1 19 | return (num_items, []) 20 | 21 | 22 | d = defaultdict(tuple_counter) 23 | 24 | 25 | def counter_letter_frequency(sentence): 26 | return Counter(sentence) 27 | 28 | 29 | CHARACTERS = list(string.ascii_letters) + [" "] 30 | 31 | 32 | def list_letter_frequency(sentence): 33 | frequencies = [(c, 0) for c in CHARACTERS] 34 | for letter in sentence: 35 | index = CHARACTERS.index(letter) 36 | frequencies[index] = (letter, frequencies[index][1] + 1) 37 | return frequencies 38 | -------------------------------------------------------------------------------- /Chapter06/song_sets.py: -------------------------------------------------------------------------------- 1 | song_library = [ 2 | ("Phantom Of The Opera", "Sarah Brightman"), 3 | ("Knocking On Heaven's Door", "Guns N' Roses"), 4 | ("Captain Nemo", "Sarah Brightman"), 5 | ("Patterns In The Ivy", "Opeth"), 6 | ("November Rain", "Guns N' Roses"), 7 | ("Beautiful", "Sarah Brightman"), 8 | ("Mal's Song", "Vixy and Tony"), 9 | ] 10 | 11 | artists = set() 12 | for song, artist in song_library: 13 | artists.add(artist) 14 | 15 | print(artists) 16 | 17 | first_artists = { 18 | "Sarah Brightman", 19 | "Guns N' Roses", 20 | "Opeth", 21 | "Vixy and Tony", 22 | } 23 | 24 | second_artists = {"Nickelback", "Guns N' Roses", "Savage Garden"} 25 | 26 | print("All: {}".format(first_artists.union(second_artists))) 27 | print("Both: {}".format(second_artists.intersection(first_artists))) 28 | print( 29 | "Either but not both: {}".format( 30 | first_artists.symmetric_difference(second_artists) 31 | ) 32 | ) 33 | 34 | bands = {"Guns N' Roses", "Opeth"} 35 | 36 | print("first_artists is to bands:") 37 | print("issuperset: {}".format(first_artists.issuperset(bands))) 38 | print("issubset: {}".format(first_artists.issubset(bands))) 39 | print("difference: {}".format(first_artists.difference(bands))) 40 | print("*" * 20) 41 | print("bands is to first_artists:") 42 | print("issuperset: {}".format(bands.issuperset(first_artists))) 43 | print("issubset: {}".format(bands.issubset(first_artists))) 44 | print("difference: {}".format(bands.difference(first_artists))) 45 | -------------------------------------------------------------------------------- /Chapter06/sorting_lists.py: -------------------------------------------------------------------------------- 1 | from functools import total_ordering 2 | 3 | 4 | @total_ordering 5 | class WeirdSortee: 6 | def __init__(self, string, number, sort_num): 7 | self.string = string 8 | self.number = number 9 | self.sort_num = sort_num 10 | 11 | def __lt__(self, object): 12 | if self.sort_num: 13 | return self.number < object.number 14 | return self.string < object.string 15 | 16 | def __repr__(self): 17 | return f"{self.string}:{self.number}" 18 | 19 | def __eq__(self, object): 20 | return all( 21 | ( 22 | self.string == object.string, 23 | self.number == object.number, 24 | self.sort_num == object.number, 25 | ) 26 | ) 27 | 28 | -------------------------------------------------------------------------------- /Chapter06/tuple_stocks.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from collections import namedtuple 3 | 4 | 5 | # tuples 6 | def middle(stock, date): 7 | symbol, current, high, low = stock 8 | return (((high + low) / 2), date) 9 | 10 | 11 | mid_value, date = middle( 12 | ("FB", 177.46, 178.67, 175.79), datetime.date(2014, 10, 31) 13 | ) 14 | 15 | # namedtuple 16 | Stock = namedtuple("Stock", ["symbol", "current", "high", "low"]) 17 | stock = Stock("FB", 177.46, high=178.67, low=175.79) 18 | -------------------------------------------------------------------------------- /Chapter06/using_dictionaries.py: -------------------------------------------------------------------------------- 1 | stocks = { 2 | "GOOG": (1235.20, 1242.54, 1231.06), 3 | "MSFT": (110.41, 110.45, 109.84), 4 | } 5 | 6 | random_keys = {} 7 | random_keys["astring"] = "somestring" 8 | random_keys[5] = "aninteger" 9 | random_keys[25.2] = "floats work too" 10 | random_keys[("abc", 123)] = "so do tuples" 11 | 12 | 13 | class AnObject: 14 | def __init__(self, avalue): 15 | self.avalue = avalue 16 | 17 | 18 | my_object = AnObject(14) 19 | random_keys[my_object] = "We can even store objects" 20 | my_object.avalue = 12 21 | try: 22 | random_keys[[1, 2, 3]] = "we can't store lists though" 23 | except: 24 | print("unable to store list\n") 25 | 26 | for key, value in random_keys.items(): 27 | print("{} has value {}".format(key, value)) 28 | -------------------------------------------------------------------------------- /Chapter07/Case Study_ Mailing Lists/addresses.db: -------------------------------------------------------------------------------- 1 | friend1@example.com friends 2 | friend2@example.com family 3 | family1@example.com family 4 | friend3@example.com friends 5 | -------------------------------------------------------------------------------- /Chapter07/Case Study_ Mailing Lists/mailing_list.py: -------------------------------------------------------------------------------- 1 | from contextlib import suppress 2 | from collections import defaultdict 3 | import smtplib 4 | from email.mime.text import MIMEText 5 | 6 | 7 | def send_email( 8 | subject, 9 | message, 10 | from_addr, 11 | *to_addrs, 12 | host="localhost", 13 | port=1025, 14 | headers=None 15 | ): 16 | headers = headers if headers else {} 17 | 18 | email = MIMEText(message) 19 | email["Subject"] = subject 20 | email["From"] = from_addr 21 | for header, value in headers.items(): 22 | email[header] = value 23 | 24 | sender = smtplib.SMTP(host, port) 25 | for addr in to_addrs: 26 | del email["To"] 27 | email["To"] = addr 28 | sender.sendmail(from_addr, addr, email.as_string()) 29 | sender.quit() 30 | 31 | 32 | class MailingList: 33 | """Manage groups of e-mail addresses for sending e-mails.""" 34 | 35 | def __init__(self, data_file): 36 | self.data_file = data_file 37 | self.email_map = defaultdict(set) 38 | 39 | def add_to_group(self, email, group): 40 | self.email_map[email].add(group) 41 | 42 | def emails_in_groups(self, *groups): 43 | groups = set(groups) 44 | emails = set() 45 | for e, g in self.email_map.items(): 46 | if g & groups: 47 | emails.add(e) 48 | return emails 49 | 50 | def send_mailing( 51 | self, subject, message, from_addr, *groups, headers=None 52 | ): 53 | emails = self.emails_in_groups(*groups) 54 | send_email( 55 | subject, message, from_addr, *emails, headers=headers 56 | ) 57 | 58 | def save(self): 59 | with open(self.data_file, "w") as file: 60 | for email, groups in self.email_map.items(): 61 | file.write("{} {}\n".format(email, ",".join(groups))) 62 | 63 | def load(self): 64 | self.email_map = defaultdict(set) 65 | with suppress(IOError): 66 | with open(self.data_file) as file: 67 | for line in file: 68 | email, groups = line.strip().split(" ") 69 | groups = set(groups.split(",")) 70 | self.email_map[email] = groups 71 | 72 | def __enter__(self): 73 | self.load() 74 | return self 75 | 76 | def __exit__(self, type, value, tb): 77 | self.save() 78 | -------------------------------------------------------------------------------- /Chapter07/augmented_move.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import os.path 3 | 4 | 5 | def augmented_move( 6 | target_folder, *filenames, verbose=False, **specific 7 | ): 8 | """Move all filenames into the target_folder, allowing 9 | specific treatment of certain files.""" 10 | 11 | def print_verbose(message, filename): 12 | """print the message only if verbose is enabled""" 13 | if verbose: 14 | print(message.format(filename)) 15 | 16 | for filename in filenames: 17 | target_path = os.path.join(target_folder, filename) 18 | if filename in specific: 19 | if specific[filename] == "ignore": 20 | print_verbose("Ignoring {0}", filename) 21 | elif specific[filename] == "copy": 22 | print_verbose("Copying {0}", filename) 23 | shutil.copyfile(filename, target_path) 24 | else: 25 | print_verbose("Moving {0}", filename) 26 | shutil.move(filename, target_path) 27 | 28 | 29 | augmented_move( 30 | "target", "augmented_move.py", "custom_sequence.py", verbose=True 31 | ) 32 | 33 | -------------------------------------------------------------------------------- /Chapter07/custom_sequence.py: -------------------------------------------------------------------------------- 1 | normal_list = [1, 2, 3, 4, 5] 2 | 3 | 4 | class CustomSequence: 5 | def __len__(self): 6 | return 5 7 | 8 | def __getitem__(self, index): 9 | return f"x{index}" 10 | 11 | 12 | class FunkyBackwards: 13 | def __reversed__(self): 14 | return "BACKWARDS!" 15 | 16 | 17 | for seq in normal_list, CustomSequence(), FunkyBackwards(): 18 | print(f"\n{seq.__class__.__name__}: ", end="") 19 | for item in reversed(seq): 20 | print(item, end=", ") 21 | -------------------------------------------------------------------------------- /Chapter07/default_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 | 10 | def __init__(self, **kwargs): 11 | self.options = dict(Options.default_options) 12 | self.options.update(kwargs) 13 | 14 | def __getitem__(self, key): 15 | return self.options[key] 16 | -------------------------------------------------------------------------------- /Chapter07/enumerate_lines.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | filename = sys.argv[1] 4 | 5 | with open(filename) as file: 6 | for index, line in enumerate(file): 7 | print(f"{index+1}: {line}", end="") 8 | -------------------------------------------------------------------------------- /Chapter07/event_timer.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import time 3 | 4 | 5 | class TimedEvent: 6 | def __init__(self, endtime, callback): 7 | self.endtime = endtime 8 | self.callback = callback 9 | 10 | def ready(self): 11 | return self.endtime <= datetime.datetime.now() 12 | 13 | 14 | class Timer: 15 | def __init__(self): 16 | self.events = [] 17 | 18 | def call_after(self, delay, callback): 19 | end_time = datetime.datetime.now() + datetime.timedelta( 20 | seconds=delay 21 | ) 22 | 23 | self.events.append(TimedEvent(end_time, callback)) 24 | 25 | def run(self): 26 | while True: 27 | ready_events = (e for e in self.events if e.ready()) 28 | for event in ready_events: 29 | event.callback(self) 30 | self.events.remove(event) 31 | time.sleep(0.5) 32 | 33 | 34 | def format_time(message, *args): 35 | now = datetime.datetime.now() 36 | print(f"{now:%I:%M:%S}: {message}") 37 | 38 | 39 | def one(timer): 40 | format_time("Called One") 41 | 42 | 43 | def two(timer): 44 | format_time("Called Two") 45 | 46 | 47 | def three(timer): 48 | format_time("Called Three") 49 | 50 | 51 | class Repeater: 52 | def __init__(self): 53 | self.count = 0 54 | 55 | def __call__(self, timer): 56 | format_time(f"repeat {self.count}") 57 | self.count += 1 58 | timer.call_after(5, self) 59 | 60 | 61 | timer = Timer() 62 | timer.call_after(1, one) 63 | timer.call_after(2, one) 64 | timer.call_after(2, two) 65 | timer.call_after(4, two) 66 | timer.call_after(3, three) 67 | timer.call_after(6, three) 68 | repeater = Repeater() 69 | timer.call_after(5, repeater) 70 | format_time("Starting") 71 | timer.run() 72 | -------------------------------------------------------------------------------- /Chapter07/passing_functions.py: -------------------------------------------------------------------------------- 1 | def my_function(): 2 | print("The Function Was Called") 3 | 4 | 5 | my_function.description = "A silly function" 6 | 7 | 8 | def second_function(): 9 | print("The second was called") 10 | 11 | 12 | second_function.description = "A sillier function." 13 | 14 | 15 | def another_function(function): 16 | print("The description:", end=" ") 17 | print(function.description) 18 | print("The name:", end=" ") 19 | print(function.__name__) 20 | print("The class:", end=" ") 21 | print(function.__class__) 22 | print("Now I'll call the function passed in") 23 | function() 24 | 25 | 26 | another_function(my_function) 27 | another_function(second_function) 28 | -------------------------------------------------------------------------------- /Chapter08/Case Study_ Templating Engine/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 | -------------------------------------------------------------------------------- /Chapter08/Case Study_ Templating Engine/done.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

This is the title of the front page

6 | First Link 7 | Second Link 8 | 9 |

My name is Dusty. This is the content of my front page. It goes below the menu.

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
Favourite Books
Thief Of Time
The Thief
Snow Crash
Lathe Of Heaven
36 | 37 | 38 | 39 | Copyright © Today 40 | -------------------------------------------------------------------------------- /Chapter08/Case Study_ Templating Engine/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Chapter08/Case Study_ Templating Engine/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Chapter08/Case Study_ Templating Engine/index.html: -------------------------------------------------------------------------------- 1 | /** include header.html **/ 2 |

This is the title of the front page

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

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

5 | 6 | 7 | 8 | 9 | /** loopover book_list **/ 10 | 11 | 12 | 13 | 14 | /** endloop **/ 15 |
Favourite Books
/** loopvar **/
16 | /** include footer.html **/ Copyright © Today 17 | -------------------------------------------------------------------------------- /Chapter08/Case Study_ Templating Engine/menu.html: -------------------------------------------------------------------------------- 1 | First Link 2 | Second Link 3 | -------------------------------------------------------------------------------- /Chapter08/Case Study_ Templating Engine/templater.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 | 12 | class TemplateEngine: 13 | def __init__(self, infilename, outfilename, contextfilename): 14 | self.template = open(infilename).read() 15 | self.working_dir = Path(infilename).absolute().parent 16 | self.pos = 0 17 | self.outfile = open(outfilename, "w") 18 | with open(contextfilename) as contextfile: 19 | self.context = json.load(contextfile) 20 | 21 | def process(self): 22 | match = DIRECTIVE_RE.search(self.template, pos=self.pos) 23 | while match: 24 | self.outfile.write(self.template[self.pos : match.start()]) 25 | directive, argument = match.groups() 26 | method_name = "process_{}".format(directive) 27 | getattr(self, method_name)(match, argument) 28 | match = DIRECTIVE_RE.search(self.template, pos=self.pos) 29 | self.outfile.write(self.template[self.pos :]) 30 | 31 | def process_include(self, match, argument): 32 | with (self.working_dir / argument).open() as includefile: 33 | self.outfile.write(includefile.read()) 34 | self.pos = match.end() 35 | 36 | def process_variable(self, match, argument): 37 | self.outfile.write(self.context.get(argument, "")) 38 | self.pos = match.end() 39 | 40 | def process_loopover(self, match, argument): 41 | self.loop_index = 0 42 | self.loop_list = self.context.get(argument, []) 43 | self.pos = self.loop_pos = match.end() 44 | 45 | def process_loopvar(self, match, argument): 46 | self.outfile.write(self.loop_list[self.loop_index]) 47 | self.pos = match.end() 48 | 49 | def process_endloop(self, match, argument): 50 | self.loop_index += 1 51 | if self.loop_index >= len(self.loop_list): 52 | self.pos = match.end() 53 | del self.loop_index 54 | del self.loop_list 55 | del self.loop_pos 56 | else: 57 | self.pos = self.loop_pos 58 | 59 | 60 | if __name__ == "__main__": 61 | infilename, outfilename, contextfilename = sys.argv[1:] 62 | engine = TemplateEngine(infilename, outfilename, contextfilename) 63 | engine.process() 64 | -------------------------------------------------------------------------------- /Chapter08/email_formats.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 | 7 | formatted = f""" 8 | From: <{emails[0]}> 9 | To: <{emails[1]}> 10 | Subject: {message['subject']} 11 | {message['message']}""" 12 | print(formatted) 13 | 14 | 15 | message["emails"] = emails 16 | 17 | formatted = f""" 18 | From: <{message['emails'][0]}> 19 | To: <{message['emails'][1]}> 20 | Subject: {message['subject']} 21 | {message['message']}""" 22 | print(formatted) 23 | 24 | 25 | class EMail: 26 | def __init__(self, from_addr, to_addr, subject, message): 27 | self.from_addr = from_addr 28 | self.to_addr = to_addr 29 | self.subject = subject 30 | self._message = message 31 | 32 | def message(self): 33 | return self._message 34 | 35 | 36 | email = EMail( 37 | "a@example.com", 38 | "b@example.com", 39 | "You Have Mail!", 40 | "Here's some mail for you!", 41 | ) 42 | 43 | formatted = f""" 44 | From: <{email.from_addr}> 45 | To: <{email.to_addr}> 46 | Subject: {email.subject} 47 | 48 | {email.message()}""" 49 | print(formatted) 50 | -------------------------------------------------------------------------------- /Chapter08/encoding_bytes.py: -------------------------------------------------------------------------------- 1 | characters = b"\x63\x6c\x69\x63\x68\xe9" 2 | print(characters) 3 | print(characters.decode("latin-1")) 4 | 5 | characters = "cliché" 6 | print(characters.encode("UTF-8")) 7 | print(characters.encode("latin-1")) 8 | print(characters.encode("CP437")) 9 | # print(characters.encode("ascii")) 10 | 11 | b = bytearray(b"abcdefgh") 12 | b[4:6] = b"\x15\xa3" 13 | print(b) 14 | 15 | b = bytearray(b"abcdef") 16 | b[3] = ord(b"g") 17 | b[4] = 68 18 | print(b) 19 | -------------------------------------------------------------------------------- /Chapter08/format_invoice.py: -------------------------------------------------------------------------------- 1 | # This won't render right 2 | subtotal = 12.32 3 | tax = subtotal * 0.07 4 | total = subtotal + tax 5 | 6 | print(f"Sub: ${subtotal} Tax: ${tax} Total: ${total}") 7 | 8 | print(f"Sub: ${subtotal:0.2f} Tax: ${tax:0.2f} Total: ${total:0.2f}") 9 | 10 | orders = [("burger", 2, 5), ("fries", 3.5, 1), ("cola", 1.75, 3)] 11 | 12 | print("PRODUCT QUANTITY PRICE SUBTOTAL") 13 | for product, price, quantity in orders: 14 | subtotal = price * quantity 15 | print( 16 | f"{product:10s}{quantity: ^9d} " 17 | f"${price: <8.2f}${subtotal: >7.2f}" 18 | ) 19 | -------------------------------------------------------------------------------- /Chapter08/fun_with_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 | 11 | 12 | # email address 13 | pattern = "^[a-zA-Z.]+@([a-z.]*\.[a-z]+)$" 14 | search_string = "some.user@example.com" 15 | match = re.match(pattern, search_string) 16 | 17 | if match: 18 | domain = match.groups()[0] 19 | print(domain) 20 | -------------------------------------------------------------------------------- /Chapter08/json_serializer.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | class ContactEncoder(json.JSONEncoder): 5 | def default(self, obj): 6 | if isinstance(obj, Contact): 7 | return { 8 | "is_contact": True, 9 | "first": obj.first, 10 | "last": obj.last, 11 | "full": obj.full_name, 12 | } 13 | return super().default(obj) 14 | 15 | 16 | def decode_contact(dic): 17 | if dic.get("is_contact"): 18 | return Contact(dic["first"], dic["last"]) 19 | else: 20 | return dic 21 | 22 | 23 | class Contact: 24 | def __init__(self, first, last): 25 | self.first = first 26 | self.last = last 27 | 28 | @property 29 | def full_name(self): 30 | return "{} {}".format(self.first, self.last) 31 | 32 | -------------------------------------------------------------------------------- /Chapter08/pickled_list: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Third-Edition/6dc21b5e65465712c9923aabc9b713d327412258/Chapter08/pickled_list -------------------------------------------------------------------------------- /Chapter08/pickling.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | 3 | some_data = [ 4 | "a list", 5 | "containing", 6 | 5, 7 | "values including another list", 8 | ["inner", "list"], 9 | ] 10 | 11 | with open("pickled_list", "wb") as file: 12 | pickle.dump(some_data, file) 13 | 14 | with open("pickled_list", "rb") as file: 15 | loaded_data = pickle.load(file) 16 | 17 | print(loaded_data) 18 | assert loaded_data == some_data 19 | -------------------------------------------------------------------------------- /Chapter08/regex_check.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 | -------------------------------------------------------------------------------- /Chapter08/sloc.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | 3 | 4 | def count_sloc(dir_path): 5 | sloc = 0 6 | for path in dir_path.iterdir(): 7 | if path.name.startswith("."): 8 | continue 9 | if path.is_dir(): 10 | sloc += count_sloc(path) 11 | continue 12 | if path.suffix != ".py": 13 | continue 14 | with path.open() as file: 15 | for line in file: 16 | line = line.strip() 17 | if line and not line.startswith("#"): 18 | sloc += 1 19 | return sloc 20 | 21 | 22 | root_path = pathlib.Path(".") 23 | 24 | print(f"{count_sloc(root_path)} lines of python code") 25 | 26 | -------------------------------------------------------------------------------- /Chapter08/string_formatting.py: -------------------------------------------------------------------------------- 1 | name = "Dusty" 2 | activity = "writing" 3 | formatted = f"Hello {name}, you are currently {activity}." 4 | print(formatted) 5 | 6 | 7 | classname = "MyClass" 8 | python_code = "print('hello world')" 9 | template = f""" 10 | public class {classname} {{ 11 | public static void main(String[] args) {{ 12 | System.out.println("{python_code}"); 13 | }} 14 | }}""" 15 | 16 | print(template) 17 | 18 | -------------------------------------------------------------------------------- /Chapter08/update_url.py: -------------------------------------------------------------------------------- 1 | from threading import Timer 2 | import datetime 3 | from urllib.request import urlopen 4 | 5 | 6 | class UpdatedURL: 7 | def __init__(self, url): 8 | self.url = url 9 | self.contents = "" 10 | self.last_updated = None 11 | self.update() 12 | 13 | def update(self): 14 | self.contents = urlopen(self.url).read() 15 | self.last_updated = datetime.datetime.now() 16 | self.schedule() 17 | 18 | def schedule(self): 19 | self.timer = Timer(3600, self.update) 20 | self.timer.setDaemon(True) 21 | self.timer.start() 22 | 23 | def __getstate__(self): 24 | new_state = self.__dict__.copy() 25 | if "timer" in new_state: 26 | del new_state["timer"] 27 | return new_state 28 | 29 | def __setstate__(self, data): 30 | self.__dict__ = data 31 | self.schedule() 32 | -------------------------------------------------------------------------------- /Chapter09/Case Study_ Machine Learning/check_output.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import csv 3 | 4 | 5 | class Application(tk.Frame): 6 | def __init__(self, master=None): 7 | super().__init__(master) 8 | self.grid(sticky="news") 9 | master.columnconfigure(0, weight=1) 10 | master.rowconfigure(0, weight=1) 11 | self.csv_reader = csv.reader(open("output.csv")) 12 | self.create_widgets() 13 | self.total_count = 0 14 | self.right_count = 0 15 | 16 | def next_color(self): 17 | return next(self.csv_reader) 18 | 19 | def mk_grid(self, widget, column, row, columnspan=1): 20 | widget.grid( 21 | column=column, row=row, columnspan=columnspan, sticky="news" 22 | ) 23 | 24 | def create_widgets(self): 25 | color_text, color_bg = self.next_color() 26 | self.color_box = tk.Label( 27 | self, bg=color_bg, width="30", height="15" 28 | ) 29 | self.mk_grid(self.color_box, 0, 0, 2) 30 | 31 | self.color_label = tk.Label(self, text=color_text, height="3") 32 | self.mk_grid(self.color_label, 0, 1, 2) 33 | 34 | self.no_button = tk.Button( 35 | self, command=self.count_next, text="No" 36 | ) 37 | self.mk_grid(self.no_button, 0, 2) 38 | 39 | self.yes_button = tk.Button( 40 | self, command=self.count_yes, text="Yes" 41 | ) 42 | self.mk_grid(self.yes_button, 1, 2) 43 | 44 | self.percent_accurate = tk.Label(self, height="3", text="0%") 45 | self.mk_grid(self.percent_accurate, 0, 3, 2) 46 | 47 | self.quit = tk.Button( 48 | self, text="Quit", command=root.destroy, bg="#ffaabb" 49 | ) 50 | self.mk_grid(self.quit, 0, 4, 2) 51 | 52 | def count_yes(self): 53 | self.right_count += 1 54 | self.count_next() 55 | 56 | def count_next(self): 57 | self.total_count += 1 58 | percentage = self.right_count / self.total_count 59 | self.percent_accurate["text"] = f"{percentage:.0%}" 60 | try: 61 | color_text, color_bg = self.next_color() 62 | except StopIteration: 63 | color_text = "DONE" 64 | color_bg = "#ffffff" 65 | self.color_box["text"] = "DONE" 66 | self.yes_button["state"] = tk.DISABLED 67 | self.no_button["state"] = tk.DISABLED 68 | self.color_label["text"] = color_text 69 | self.color_box["bg"] = color_bg 70 | 71 | 72 | root = tk.Tk() 73 | app = Application(master=root) 74 | app.mainloop() 75 | -------------------------------------------------------------------------------- /Chapter09/Case Study_ Machine Learning/colors.csv: -------------------------------------------------------------------------------- 1 | Grey,#dcdcdc 2 | Grey,#656565 3 | Grey,#464646 4 | Grey,#c2c2c2 5 | Grey,#262626 6 | Grey,#030303 7 | Grey,#0a0a0a 8 | Grey,#c3c3c3 9 | Blue,#1d28ff 10 | Green,#3cafa8 11 | Purple,#9560cc 12 | Green,#075037 13 | Yellow,#e3e7d8 14 | Yellow,#a2a923 15 | Red,#f44215 16 | Grey,#050f06 17 | Red,#7e023c 18 | Blue,#477cf3 19 | Orange,#fb7753 20 | Green,#979533 21 | Green,#d9ddc9 22 | Purple,#8d5eca 23 | Green,#2cfd49 24 | Purple,#813cbd 25 | Green,#14d364 26 | Pink,#fbaca5 27 | Purple,#7f2555 28 | Orange,#f9b133 29 | Purple,#b293e6 30 | Orange,#f09d65 31 | Orange,#c18b0f 32 | Purple,#8c4e8b 33 | Green,#38c99c 34 | Blue,#9cb1c7 35 | Purple,#9a40f1 36 | Purple,#a609a7 37 | Pink,#fc4d8a 38 | Purple,#c010c8 39 | Pink,#f15bde 40 | Pink,#eb66d9 41 | Green,#1ef34a 42 | Orange,#ca7064 43 | Green,#6edd13 44 | Purple,#814faf 45 | Yellow,#c7c26d 46 | Orange,#61442c 47 | Green,#67f496 48 | Purple,#c757d5 49 | Blue,#106a98 50 | Blue,#46239e 51 | Green,#06b09b 52 | Purple,#e1a7f2 53 | Pink,#cc0e65 54 | Pink,#d40491 55 | Blue,#a4bdfa 56 | Green,#30882f 57 | Pink,#f47aad 58 | Blue,#0e9eb4 59 | Green,#475939 60 | Blue,#1a3bad 61 | Green,#83ddb2 62 | Grey,#baaec9 63 | Grey,#8aa28d 64 | Blue,#533eda 65 | Pink,#854f4f 66 | Green,#a2f15e 67 | Purple,#51147a 68 | Green,#347e66 69 | Green,#4b836a 70 | Green,#6abab1 71 | Blue,#4b1edd 72 | Yellow,#b7dd04 73 | Purple,#c196f0 74 | Pink,#e52a6e 75 | Green,#8dce4d 76 | Blue,#02c0cc 77 | Blue,#3cfad5 78 | Red,#793c4b 79 | Yellow,#c2cc24 80 | Orange,#df8d31 81 | Yellow,#f1eba4 82 | Purple,#a18ccb 83 | Pink,#ef95f0 84 | Blue,#4941e7 85 | Yellow,#c4b01e 86 | Blue,#6f9ff5 87 | Red,#da1806 88 | Grey,#120729 89 | Red,#c60e05 90 | Pink,#e382cd 91 | Purple,#793cae 92 | Grey,#310e24 93 | Purple,#5e26bb 94 | Purple,#560673 95 | Blue,#26379d 96 | Green,#206f75 97 | Blue,#0ec4dd 98 | Blue,#119fd0 99 | Purple,#b926fc 100 | Purple,#8d8fd3 101 | Orange,#edaa6e 102 | Purple,#8622c5 103 | Blue,#2d3b96 104 | Blue,#1a86e0 105 | Green,#527900 106 | Grey,#f8daf2 107 | Red,#ec3f3b 108 | Green,#59e013 109 | Green,#22f084 110 | Red,#cc221a 111 | Purple,#9440d5 112 | Pink,#ed437a 113 | Yellow,#d0be07 114 | Grey,#7c6770 115 | Orange,#e1a68a 116 | Grey,#b0909b 117 | Orange,#ef4e0a 118 | Blue,#7c8eee 119 | Blue,#5accea 120 | Grey,#794f5d 121 | Yellow,#f0d12a 122 | Green,#0fa63a 123 | Blue,#43a0e7 124 | Pink,#f82483 125 | Green,#22c159 126 | Yellow,#ddc01a 127 | Pink,#fc1fb6 128 | Green,#72fbd3 129 | Green,#1af539 130 | Purple,#a656d9 131 | Red,#91422f 132 | Grey,#6c5e7b 133 | Orange,#82612d 134 | Green,#83de64 135 | Grey,#110e00 136 | Orange,#605407 137 | Green,#64b460 138 | Blue,#0bf6fc 139 | Purple,#ed5bf5 140 | Red,#953e51 141 | Red,#b51310 142 | Blue,#002474 143 | Blue,#0e30e5 144 | Green,#148301 145 | Blue,#6bb3b7 146 | Purple,#680067 147 | Blue,#1d4bf1 148 | Grey,#525f63 149 | Blue,#17255e 150 | Purple,#8666cf 151 | Orange,#a35444 152 | Green,#4cd541 153 | Green,#68c34d 154 | Blue,#0939a3 155 | Purple,#45194b 156 | Purple,#944a7e 157 | Orange,#ff9863 158 | Green,#0afa42 159 | Grey,#7ec2bf 160 | Yellow,#f5bd32 161 | Green,#9ccd67 162 | Pink,#e0a0a8 163 | Orange,#ba8e27 164 | Green,#338600 165 | Blue,#4612bb 166 | Blue,#0457a0 167 | Green,#18d43b 168 | Yellow,#bdad30 169 | Pink,#f0b5de 170 | Grey,#0a1308 171 | Green,#8ebd82 172 | Green,#166c49 173 | Pink,#dea6b9 174 | Green,#328d96 175 | Pink,#fe236a 176 | Red,#82372b 177 | Yellow,#bae326 178 | Green,#87f03a 179 | Grey,#819cb2 180 | Yellow,#d7e531 181 | Grey,#b19bae 182 | Blue,#064bfb 183 | Purple,#cd3ab8 184 | Red,#ba2349 185 | Green,#61dda7 186 | Purple,#d83fba 187 | Green,#528048 188 | Blue,#32f6f6 189 | Orange,#f29f21 190 | Blue,#24335b 191 | Pink,#be1e77 192 | Grey,#98928f 193 | Pink,#ee5b83 194 | Blue,#011d79 195 | Purple,#c529c3 196 | Blue,#042086 197 | Blue,#4117e5 198 | Orange,#bd9971 199 | Red,#e52a27 200 | Blue,#2f55f1 201 | Green,#17b942 202 | Purple,#7e20c2 203 | Green,#b6c569 204 | Blue,#4d2cba 205 | Pink,#c34494 206 | Pink,#f48db0 207 | Green,#afbc57 208 | Blue,#1007d2 209 | Purple,#8e0bb7 210 | Pink,#eb0a73 211 | Green,#8de37a 212 | Orange,#bb8e37 213 | Red,#d0184b 214 | Purple,#6f3152 215 | Blue,#3638b1 216 | Blue,#6d95e4 217 | Green,#b3f232 218 | Green,#90e97c 219 | Blue,#5170ae 220 | Purple,#e15efe 221 | Green,#45c0bb 222 | Green,#bcee6d 223 | Green,#5ca85b 224 | Green,#04711c 225 | Pink,#cf2e9a 226 | Purple,#d5b3f6 227 | Blue,#253af0 228 | Green,#149786 229 | Pink,#dea1b6 230 | Blue,#246e95 231 | Green,#014a0c 232 | Green,#0ea997 233 | Purple,#7745bd 234 | Blue,#11d2fd 235 | Yellow,#a89e1a 236 | Yellow,#cef982 237 | Purple,#592680 238 | Green,#0ec3a2 239 | Green,#74c8a2 240 | Blue,#3561e7 241 | Green,#48ceb6 242 | Green,#27bcab 243 | Red,#a10524 244 | Grey,#5889b1 245 | Blue,#2798bd 246 | Yellow,#ced89d 247 | Green,#2b7e79 248 | Red,#ae2e3d 249 | Blue,#3e8eda 250 | Pink,#f4a49f 251 | Blue,#0e84aa 252 | Green,#43790d 253 | Grey,#010f08 254 | Yellow,#adac1f 255 | Blue,#09308e 256 | Blue,#472aea 257 | Green,#1aa216 258 | Purple,#b29ac5 259 | Yellow,#d8cf19 260 | Green,#a2cfb0 261 | Yellow,#ede130 262 | -------------------------------------------------------------------------------- /Chapter09/Case Study_ Machine Learning/machine_learn.py: -------------------------------------------------------------------------------- 1 | import csv 2 | from random import randint 3 | from collections import Counter 4 | 5 | dataset_filename = "colors.csv" 6 | 7 | 8 | def hex_to_rgb(hex_color): 9 | return tuple(int(hex_color[i : i + 2], 16) for i in range(1, 6, 2)) 10 | 11 | 12 | def load_colors(filename): 13 | with open(filename) as dataset_file: 14 | lines = csv.reader(dataset_file) 15 | for line in lines: 16 | label, hex_color = line 17 | yield (hex_to_rgb(hex_color), label) 18 | 19 | 20 | def generate_colors(count=100): 21 | for i in range(count): 22 | yield (randint(0, 255), randint(0, 255), randint(0, 255)) 23 | 24 | 25 | def color_distance(color1, color2): 26 | channels = zip(color1, color2) 27 | sum_distance_squared = 0 28 | for c1, c2 in channels: 29 | sum_distance_squared += (c1 - c2) ** 2 30 | return sum_distance_squared 31 | 32 | 33 | def nearest_neighbors(model_colors, target_colors, num_neighbors=5): 34 | model_colors = list(model_colors) 35 | for target in target_colors: 36 | distances = sorted( 37 | ((color_distance(c[0], target), c) for c in model_colors) 38 | ) 39 | yield target, [d[1] for d in distances[:num_neighbors]] 40 | 41 | 42 | def name_colors(model_colors, target_colors, num_neighbors=5): 43 | for target, near in nearest_neighbors( 44 | model_colors, target_colors, num_neighbors=5 45 | ): 46 | name_guess = Counter(n[1] for n in near).most_common()[0][0] 47 | yield target, name_guess 48 | 49 | 50 | def write_results(colors, filename="output.csv"): 51 | with open(filename, "w") as file: 52 | writer = csv.writer(file) 53 | for (r, g, b), name in colors: 54 | writer.writerow([name, f"#{r:02x}{g:02x}{b:02x}"]) 55 | 56 | 57 | def process_colors(dataset_filename="colors.csv"): 58 | model_colors = load_colors(dataset_filename) 59 | colors = name_colors(model_colors, generate_colors(), 5) 60 | write_results(colors) 61 | 62 | 63 | if __name__ == "__main__": 64 | process_colors() 65 | -------------------------------------------------------------------------------- /Chapter09/Case Study_ Machine Learning/manual_classify.py: -------------------------------------------------------------------------------- 1 | import random 2 | import tkinter as tk 3 | import csv 4 | 5 | 6 | class Application(tk.Frame): 7 | def __init__(self, master=None): 8 | super().__init__(master) 9 | self.grid(sticky="news") 10 | master.columnconfigure(0, weight=1) 11 | master.rowconfigure(0, weight=1) 12 | self.create_widgets() 13 | self.file = csv.writer(open("colors.csv", "a")) 14 | 15 | def create_color_button(self, label, column, row): 16 | button = tk.Button( 17 | self, command=lambda: self.click_color(label), text=label 18 | ) 19 | button.grid(column=column, row=row, sticky="news") 20 | 21 | def random_color(self): 22 | r = random.randint(0, 255) 23 | g = random.randint(0, 255) 24 | b = random.randint(0, 255) 25 | 26 | return f"#{r:02x}{g:02x}{b:02x}" 27 | 28 | def create_widgets(self): 29 | self.color_box = tk.Label( 30 | self, bg=self.random_color(), width="30", height="15" 31 | ) 32 | self.color_box.grid( 33 | column=0, columnspan=2, row=0, sticky="news" 34 | ) 35 | self.create_color_button("Red", 0, 1) 36 | self.create_color_button("Purple", 1, 1) 37 | self.create_color_button("Blue", 0, 2) 38 | self.create_color_button("Green", 1, 2) 39 | self.create_color_button("Yellow", 0, 3) 40 | self.create_color_button("Orange", 1, 3) 41 | self.create_color_button("Pink", 0, 4) 42 | self.create_color_button("Grey", 1, 4) 43 | self.quit = tk.Button( 44 | self, text="Quit", command=root.destroy, bg="#ffaabb" 45 | ) 46 | self.quit.grid(column=0, row=5, columnspan=2, sticky="news") 47 | 48 | def click_color(self, label): 49 | self.file.writerow([label, self.color_box["bg"]]) 50 | self.color_box["bg"] = self.random_color() 51 | 52 | 53 | root = tk.Tk() 54 | app = Application(master=root) 55 | app.mainloop() 56 | -------------------------------------------------------------------------------- /Chapter09/Case Study_ Machine Learning/output.csv: -------------------------------------------------------------------------------- 1 | Grey,#7c5f62 2 | Purple,#f002ce 3 | Green,#0fa210 4 | Blue,#228fdf 5 | Green,#07af68 6 | Purple,#922bc5 7 | Green,#04c871 8 | Green,#038771 9 | Purple,#bd57b5 10 | Green,#577209 11 | Blue,#114251 12 | Blue,#0b34c3 13 | Blue,#529ce8 14 | Purple,#dc38f6 15 | Orange,#5e270a 16 | Yellow,#dab40c 17 | Green,#13ec59 18 | Green,#048116 19 | Blue,#3a407c 20 | Yellow,#92aa14 21 | Grey,#352429 22 | Yellow,#e0cd7b 23 | Green,#1ab50a 24 | Purple,#6a3644 25 | Pink,#f311ae 26 | Orange,#e7780e 27 | Grey,#535d81 28 | Green,#6fdd6a 29 | Grey,#312142 30 | Green,#0ff5b4 31 | Green,#368b26 32 | Green,#1a6128 33 | Orange,#b98362 34 | Grey,#7d7349 35 | Red,#6b3a20 36 | Grey,#e1c8e7 37 | Blue,#1628c2 38 | Grey,#24110a 39 | Pink,#bb6885 40 | Blue,#2726eb 41 | Yellow,#faf18c 42 | Purple,#bf66b3 43 | Blue,#253c46 44 | Grey,#4c6c85 45 | Orange,#ff910d 46 | Grey,#afba9f 47 | Green,#c0c956 48 | Grey,#013707 49 | Green,#237161 50 | Blue,#2cc7eb 51 | Red,#f9202c 52 | Green,#57ca89 53 | Grey,#c0ede2 54 | Green,#719525 55 | Blue,#0b0e96 56 | Orange,#848006 57 | Green,#55d06c 58 | Yellow,#e5f480 59 | Purple,#591632 60 | Orange,#fb8d26 61 | Purple,#c269d7 62 | Blue,#68e9e5 63 | Green,#01e062 64 | Grey,#120026 65 | Green,#57fd7a 66 | Blue,#27b3f1 67 | Green,#53f61e 68 | Blue,#02109d 69 | Blue,#1e1c9a 70 | Red,#a30013 71 | Pink,#ed0759 72 | Purple,#551894 73 | Grey,#696847 74 | Yellow,#f7e418 75 | Orange,#e77c85 76 | Blue,#6583de 77 | Green,#6aca71 78 | Green,#025b64 79 | Green,#67cd2a 80 | Blue,#30dbee 81 | Blue,#41619f 82 | Grey,#cac7b6 83 | Purple,#843fae 84 | Purple,#c85dae 85 | Purple,#d03bd0 86 | Orange,#cdb676 87 | Pink,#f4bff3 88 | Green,#486e5c 89 | Green,#67d645 90 | Grey,#0e0900 91 | Blue,#858ef7 92 | Green,#60afb6 93 | Grey,#697952 94 | Green,#3c7c91 95 | Grey,#e4efff 96 | Pink,#775447 97 | Red,#6e4339 98 | Grey,#dac9bb 99 | Green,#58f88e 100 | Orange,#c4700b 101 | -------------------------------------------------------------------------------- /Chapter09/Case Study_ Machine Learning/second_edition_machine_learn_with_coroutines.py: -------------------------------------------------------------------------------- 1 | """ 2 | In the 2nd edition of Python 3 Object Oriented Programming, I presented the 3 | case study using coroutines. In the third edition, I switched to a generator 4 | based implementation instead. It is extremely unusual to use coroutines for 5 | this purpose, and the generator implementation is actually simpler to 6 | understand, read, and maintain. It's also substantially more object oriented. 7 | 8 | This file contains the coroutine based implementation. I am including it here 9 | for historical purposes in case anyone has an academic interest in this 10 | implementation. 11 | """ 12 | 13 | import csv 14 | from random import random 15 | import math 16 | from collections import Counter 17 | 18 | dataset_filename = "colors.csv" 19 | 20 | 21 | def load_colors(filename): 22 | with open(filename) as dataset_file: 23 | lines = csv.reader(dataset_file) 24 | for line in lines: 25 | yield tuple(float(y) for y in line[0:3]), line[3] 26 | 27 | 28 | def generate_colors(count=100): 29 | for i in range(count): 30 | yield (random(), random(), random()) 31 | 32 | 33 | def color_distance(color1, color2): 34 | channels = zip(color1, color2) 35 | sum_distance_squared = 0 36 | for c1, c2 in channels: 37 | sum_distance_squared += (c1 - c2) ** 2 38 | return math.sqrt(sum_distance_squared) 39 | 40 | 41 | def nearest_neighbors(model_colors, num_neighbors): 42 | model = list(model_colors) 43 | target = yield 44 | while True: 45 | distances = sorted( 46 | ((color_distance(c[0], target), c) for c in model) 47 | ) 48 | target = yield [d[1] for d in distances[0:num_neighbors]] 49 | 50 | 51 | def name_colors(get_neighbors): 52 | color = yield 53 | while True: 54 | near = get_neighbors.send(color) 55 | name_guess = Counter(n[1] for n in near).most_common(1)[0][0] 56 | color = yield name_guess 57 | 58 | 59 | def write_results(filename="output.csv"): 60 | with open(filename, "w") as file: 61 | writer = csv.writer(file) 62 | while True: 63 | color, name = yield 64 | writer.writerow(list(color) + [name]) 65 | 66 | 67 | def process_colors(dataset_filename="colors.csv"): 68 | model_colors = load_colors(dataset_filename) 69 | get_neighbors = nearest_neighbors(model_colors, 5) 70 | get_color_name = name_colors(get_neighbors) 71 | output = write_results() 72 | next(output) 73 | next(get_neighbors) 74 | next(get_color_name) 75 | 76 | for color in generate_colors(): 77 | name = get_color_name.send(color) 78 | output.send((color, name)) 79 | 80 | 81 | process_colors() 82 | -------------------------------------------------------------------------------- /Chapter09/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 | -------------------------------------------------------------------------------- /Chapter09/book_authors.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 | # set comprehension 15 | fantasy_authors = {b.author for b in books if b.genre == "fantasy"} 16 | # dict comprehension 17 | fantasy_titles = {b.title: b for b in books if b.genre == "fantasy"} 18 | -------------------------------------------------------------------------------- /Chapter09/capital_iterator.py: -------------------------------------------------------------------------------- 1 | class CapitalIterable: 2 | def __init__(self, string): 3 | self.string = string 4 | 5 | def __iter__(self): 6 | return CapitalIterator(self.string) 7 | 8 | 9 | class CapitalIterator: 10 | def __init__(self, string): 11 | self.words = [w.capitalize() for w in string.split()] 12 | self.index = 0 13 | 14 | def __next__(self): 15 | if self.index == len(self.words): 16 | raise StopIteration() 17 | 18 | word = self.words[self.index] 19 | self.index += 1 20 | return word 21 | 22 | def __iter__(self): 23 | return self 24 | -------------------------------------------------------------------------------- /Chapter09/filesystem_generator.py: -------------------------------------------------------------------------------- 1 | class File: 2 | def __init__(self, name): 3 | self.name = name 4 | 5 | 6 | class Folder(File): 7 | def __init__(self, name): 8 | super().__init__(name) 9 | self.children = [] 10 | 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 | -------------------------------------------------------------------------------- /Chapter09/list_comp.py: -------------------------------------------------------------------------------- 1 | input_strings = ["1", "5", "28", "131", "3"] 2 | # without comprehension 3 | output_integers = [] 4 | for num in input_strings: 5 | output_integers.append(int(num)) 6 | print(output_integers) 7 | 8 | 9 | # with comprehension 10 | output_integers = [int(num) for num in input_strings] 11 | print(output_integers) 12 | 13 | # with filter 14 | output_integers = [int(num) for num in input_strings if len(num) < 3] 15 | print(output_integers) 16 | -------------------------------------------------------------------------------- /Chapter09/list_comp_read_file.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | filename = sys.argv[1] 4 | 5 | with open(filename) as file: 6 | header = file.readline().strip().split("\t") 7 | contacts = [ 8 | dict(zip(header, line.strip().split("\t"))) for line in file 9 | ] 10 | 11 | for contact in contacts: 12 | print("email: {email} -- {last}, {first}".format(**contact)) 13 | 14 | -------------------------------------------------------------------------------- /Chapter09/log_loop.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 | -------------------------------------------------------------------------------- /Chapter09/sample_log.txt: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /Chapter09/score_tally.py: -------------------------------------------------------------------------------- 1 | def tally(): 2 | score = 0 3 | while True: 4 | increment = yield score 5 | score += increment 6 | -------------------------------------------------------------------------------- /Chapter09/warning_generators.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | # generator expression 4 | inname, outname = sys.argv[1:3] 5 | 6 | with open(inname) as infile: 7 | with open(outname, "w") as outfile: 8 | warnings = ( 9 | l.replace("\tWARNING", "") for l in infile if "WARNING" in l 10 | ) 11 | for l in warnings: 12 | outfile.write(l) 13 | 14 | # normal loop 15 | with open(inname) as infile: 16 | with open(outname, "w") as outfile: 17 | for l in infile: 18 | if "WARNING" in l: 19 | outfile.write(l.replace("\tWARNING", "")) 20 | 21 | 22 | # object-oriented 23 | class WarningFilter: 24 | def __init__(self, insequence): 25 | self.insequence = insequence 26 | 27 | def __iter__(self): 28 | return self 29 | 30 | def __next__(self): 31 | l = self.insequence.readline() 32 | while l and "WARNING" not in l: 33 | l = self.insequence.readline() 34 | if not l: 35 | raise StopIteration 36 | return l.replace("\tWARNING", "") 37 | 38 | 39 | with open(inname) as infile: 40 | with open(outname, "w") as outfile: 41 | filter = WarningFilter(infile) 42 | for l in filter: 43 | outfile.write(l) 44 | 45 | 46 | # Generator with yield 47 | def warnings_filter(insequence): 48 | for l in insequence: 49 | if "WARNING" in l: 50 | yield l.replace("\tWARNING", "") 51 | 52 | 53 | with open(inname) as infile: 54 | with open(outname, "w") as outfile: 55 | filter = warnings_filter(infile) 56 | for l in filter: 57 | outfile.write(l) 58 | 59 | 60 | # Generator with yield from 61 | def warnings_filter(infilename): 62 | with open(infilename) as infile: 63 | yield from ( 64 | l.replace("\tWARNING", "") for l in infile if "WARNING" in l 65 | ) 66 | 67 | 68 | filter = warnings_filter(inname) 69 | with open(outname, "w") as outfile: 70 | for l in filter: 71 | outfile.write(l) 72 | -------------------------------------------------------------------------------- /Chapter09/xfs_error_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 | try: 19 | bus = matcher.send( 20 | "(sd \S+) {}.*".format(re.escape(device)) 21 | ) 22 | serial = matcher.send("{} \(SERIAL=([^)]*)\)".format(bus)) 23 | yield serial 24 | device = matcher.send(ERROR_RE) 25 | except StopIteration: 26 | return 27 | 28 | 29 | for serial_number in get_serials("EXAMPLE_LOG.log"): 30 | print(serial_number) 31 | -------------------------------------------------------------------------------- /Chapter10/car_sales_template.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import sqlite3 3 | 4 | conn = sqlite3.connect("sales.db") 5 | 6 | conn.execute( 7 | "CREATE TABLE Sales (salesperson text, " 8 | "amt currency, year integer, model text, new boolean)" 9 | ) 10 | conn.execute( 11 | "INSERT INTO Sales values" 12 | " ('Tim', 16000, 2010, 'Honda Fit', 'true')" 13 | ) 14 | conn.execute( 15 | "INSERT INTO Sales values" 16 | " ('Tim', 9000, 2006, 'Ford Focus', 'false')" 17 | ) 18 | conn.execute( 19 | "INSERT INTO Sales values" 20 | " ('Gayle', 8000, 2004, 'Dodge Neon', 'false')" 21 | ) 22 | conn.execute( 23 | "INSERT INTO Sales values" 24 | " ('Gayle', 28000, 2009, 'Ford Mustang', 'true')" 25 | ) 26 | conn.execute( 27 | "INSERT INTO Sales values" 28 | " ('Gayle', 50000, 2010, 'Lincoln Navigator', 'true')" 29 | ) 30 | conn.execute( 31 | "INSERT INTO Sales values" 32 | " ('Don', 20000, 2008, 'Toyota Prius', 'false')" 33 | ) 34 | conn.commit() 35 | conn.close() 36 | 37 | 38 | class QueryTemplate: 39 | def connect(self): 40 | self.conn = sqlite3.connect("sales.db") 41 | 42 | def construct_query(self): 43 | raise NotImplementedError() 44 | 45 | def do_query(self): 46 | results = self.conn.execute(self.query) 47 | self.results = results.fetchall() 48 | 49 | def format_results(self): 50 | output = [] 51 | for row in self.results: 52 | row = [str(i) for i in row] 53 | output.append(", ".join(row)) 54 | self.formatted_results = "\n".join(output) 55 | 56 | def output_results(self): 57 | raise NotImplementedError() 58 | 59 | def process_format(self): 60 | self.connect() 61 | self.construct_query() 62 | self.do_query() 63 | self.format_results() 64 | self.output_results() 65 | 66 | 67 | class NewVehiclesQuery(QueryTemplate): 68 | def construct_query(self): 69 | self.query = "select * from Sales where new='true'" 70 | 71 | def output_results(self): 72 | print(self.formatted_results) 73 | 74 | 75 | class UserGrossQuery(QueryTemplate): 76 | def construct_query(self): 77 | self.query = ( 78 | "select salesperson, sum(amt) " 79 | + " from Sales group by salesperson" 80 | ) 81 | 82 | def output_results(self): 83 | filename = "gross_sales_{0}".format( 84 | datetime.date.today().strftime("%Y%m%d") 85 | ) 86 | with open(filename, "w") as outfile: 87 | outfile.write(self.formatted_results) 88 | -------------------------------------------------------------------------------- /Chapter10/inventory_observer.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 | 14 | @product.setter 15 | def product(self, value): 16 | self._product = value 17 | self._update_observers() 18 | 19 | @property 20 | def quantity(self): 21 | return self._quantity 22 | 23 | @quantity.setter 24 | def quantity(self, value): 25 | self._quantity = value 26 | self._update_observers() 27 | 28 | def _update_observers(self): 29 | for observer in self.observers: 30 | observer() 31 | 32 | 33 | class ConsoleObserver: 34 | def __init__(self, inventory): 35 | self.inventory = inventory 36 | 37 | def __call__(self): 38 | print(self.inventory.product) 39 | print(self.inventory.quantity) 40 | -------------------------------------------------------------------------------- /Chapter10/log_calls_decorator.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | def log_calls(func): 5 | def wrapper(*args, **kwargs): 6 | now = time.time() 7 | print( 8 | "Calling {0} with {1} and {2}".format( 9 | func.__name__, args, kwargs 10 | ) 11 | ) 12 | return_value = func(*args, **kwargs) 13 | print( 14 | "Executed {0} in {1}ms".format( 15 | func.__name__, time.time() - now 16 | ) 17 | ) 18 | return return_value 19 | 20 | return wrapper 21 | 22 | 23 | def test1(a, b, c): 24 | print("\ttest1 called") 25 | 26 | 27 | def test2(a, b): 28 | print("\ttest2 called") 29 | 30 | 31 | def test3(a, b): 32 | print("\ttest3 called") 33 | time.sleep(1) 34 | 35 | 36 | test1 = log_calls(test1) 37 | test2 = log_calls(test2) 38 | test3 = log_calls(test3) 39 | 40 | test1(1, 2, 3) 41 | test2(4, b=5) 42 | test3(6, 7) 43 | 44 | -------------------------------------------------------------------------------- /Chapter10/singleton_state.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 | 15 | class FirstTag: 16 | def process(self, remaining_string, parser): 17 | i_start_tag = remaining_string.find("<") 18 | i_end_tag = remaining_string.find(">") 19 | tag_name = remaining_string[i_start_tag + 1 : i_end_tag] 20 | root = Node(tag_name) 21 | parser.root = parser.current_node = root 22 | parser.state = child_node 23 | return remaining_string[i_end_tag + 1 :] 24 | 25 | 26 | class ChildNode: 27 | def process(self, remaining_string, parser): 28 | stripped = remaining_string.strip() 29 | if stripped.startswith("") 42 | tag_name = remaining_string[i_start_tag + 1 : i_end_tag] 43 | node = Node(tag_name, parser.current_node) 44 | parser.current_node.children.append(node) 45 | parser.current_node = node 46 | parser.state = child_node 47 | return remaining_string[i_end_tag + 1 :] 48 | 49 | 50 | class TextNode: 51 | def process(self, remaining_string, parser): 52 | i_start_tag = remaining_string.find("<") 53 | text = remaining_string[:i_start_tag] 54 | parser.current_node.text = text 55 | parser.state = child_node 56 | return remaining_string[i_start_tag:] 57 | 58 | 59 | class CloseTag: 60 | def process(self, remaining_string, parser): 61 | i_start_tag = remaining_string.find("<") 62 | i_end_tag = remaining_string.find(">") 63 | assert remaining_string[i_start_tag + 1] == "/" 64 | tag_name = remaining_string[i_start_tag + 2 : i_end_tag] 65 | assert tag_name == parser.current_node.tag_name 66 | parser.current_node = parser.current_node.parent 67 | parser.state = child_node 68 | return remaining_string[i_end_tag + 1 :].strip() 69 | 70 | 71 | first_tag = FirstTag() 72 | child_node = ChildNode() 73 | text_node = TextNode() 74 | open_tag = OpenTag() 75 | close_tag = CloseTag() 76 | -------------------------------------------------------------------------------- /Chapter10/socket_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 | -------------------------------------------------------------------------------- /Chapter10/socket_decorator.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import gzip 3 | from io import BytesIO 4 | 5 | 6 | class GzipSocket: 7 | def __init__(self, socket): 8 | self.socket = socket 9 | 10 | def send(self, data): 11 | buf = BytesIO() 12 | zipfile = gzip.GzipFile(fileobj=buf, mode="w") 13 | zipfile.write(data) 14 | zipfile.close() 15 | self.socket.send(buf.getvalue()) 16 | 17 | def close(self): 18 | self.socket.close() 19 | 20 | 21 | class LogSocket: 22 | def __init__(self, socket): 23 | self.socket = socket 24 | 25 | def send(self, data): 26 | print( 27 | "Sending {0} to {1}".format( 28 | data, self.socket.getpeername()[0] 29 | ) 30 | ) 31 | self.socket.send(data) 32 | 33 | def close(self): 34 | self.socket.close() 35 | 36 | 37 | def respond(client): 38 | response = input("Enter a value: ") 39 | client.send(bytes(response, "utf8")) 40 | client.close() 41 | 42 | 43 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 44 | server.bind(("localhost", 2401)) 45 | server.listen(1) 46 | try: 47 | while True: 48 | client, addr = server.accept() 49 | # respond(client) # No decorator 50 | respond(LogSocket(client)) # One decorator 51 | finally: 52 | server.close() 53 | 54 | -------------------------------------------------------------------------------- /Chapter10/tiling_strategy.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 zip(out_img.size, in_img.size) 10 | ] 11 | for x in range(num_tiles[0]): 12 | for y in range(num_tiles[1]): 13 | out_img.paste( 14 | in_img, 15 | ( 16 | in_img.size[0] * x, 17 | in_img.size[1] * y, 18 | in_img.size[0] * (x + 1), 19 | in_img.size[1] * (y + 1), 20 | ), 21 | ) 22 | return out_img 23 | 24 | 25 | class CenteredStrategy: 26 | def make_background(self, img_file, desktop_size): 27 | in_img = Image.open(img_file) 28 | out_img = Image.new("RGB", desktop_size) 29 | left = (out_img.size[0] - in_img.size[0]) // 2 30 | top = (out_img.size[1] - in_img.size[1]) // 2 31 | out_img.paste( 32 | in_img, 33 | (left, top, left + in_img.size[0], top + in_img.size[1]), 34 | ) 35 | return out_img 36 | 37 | 38 | class ScaledStrategy: 39 | def make_background(self, img_file, desktop_size): 40 | in_img = Image.open(img_file) 41 | out_img = in_img.resize(desktop_size) 42 | return out_img 43 | -------------------------------------------------------------------------------- /Chapter10/xml_example.xml: -------------------------------------------------------------------------------- 1 | 2 | Dusty Phillips 3 | Packt Publishing 4 | Python 3 Object Oriented Programming 5 | 6 | 7 | 1 8 | Object Oriented Design 9 | 10 | 11 | 2 12 | Objects In Python 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Chapter10/xml_state_parser.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 | 15 | class FirstTag: 16 | def process(self, remaining_string, parser): 17 | i_start_tag = remaining_string.find("<") 18 | i_end_tag = remaining_string.find(">") 19 | tag_name = remaining_string[i_start_tag + 1 : i_end_tag] 20 | root = Node(tag_name) 21 | parser.root = parser.current_node = root 22 | parser.state = ChildNode() 23 | return remaining_string[i_end_tag + 1 :] 24 | 25 | 26 | class ChildNode: 27 | def process(self, remaining_string, parser): 28 | stripped = remaining_string.strip() 29 | if stripped.startswith("") 42 | tag_name = remaining_string[i_start_tag + 1 : i_end_tag] 43 | node = Node(tag_name, parser.current_node) 44 | parser.current_node.children.append(node) 45 | parser.current_node = node 46 | parser.state = ChildNode() 47 | return remaining_string[i_end_tag + 1 :] 48 | 49 | 50 | class CloseTag: 51 | def process(self, remaining_string, parser): 52 | i_start_tag = remaining_string.find("<") 53 | i_end_tag = remaining_string.find(">") 54 | assert remaining_string[i_start_tag + 1] == "/" 55 | tag_name = remaining_string[i_start_tag + 2 : i_end_tag] 56 | assert tag_name == parser.current_node.tag_name 57 | parser.current_node = parser.current_node.parent 58 | parser.state = ChildNode() 59 | return remaining_string[i_end_tag + 1 :].strip() 60 | 61 | 62 | class TextNode: 63 | def process(self, remaining_string, parser): 64 | i_start_tag = remaining_string.find("<") 65 | text = remaining_string[:i_start_tag] 66 | parser.current_node.text = text 67 | parser.state = ChildNode() 68 | return remaining_string[i_start_tag:] 69 | 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 = FirstTag() 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 | 88 | if __name__ == "__main__": 89 | import sys 90 | 91 | with open(sys.argv[1]) as file: 92 | contents = file.read() 93 | p = Parser(contents) 94 | p.start() 95 | 96 | nodes = [p.root] 97 | while nodes: 98 | node = nodes.pop(0) 99 | print(node) 100 | nodes = node.children + nodes 101 | -------------------------------------------------------------------------------- /Chapter11/age_calculator_adapter.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | 4 | class AgeCalculator: 5 | def __init__(self, birthday): 6 | self.year, self.month, self.day = ( 7 | int(x) for x in birthday.split("-") 8 | ) 9 | 10 | def calculate_age(self, date): 11 | year, month, day = (int(x) for x in date.split("-")) 12 | age = year - self.year 13 | if (month, day) < (self.month, self.day): 14 | age -= 1 15 | return age 16 | 17 | 18 | class DateAgeAdapter: 19 | def _str_date(self, date): 20 | return date.strftime("%Y-%m-%d") 21 | 22 | def __init__(self, birthday): 23 | birthday = self._str_date(birthday) 24 | self.calculator = AgeCalculator(birthday) 25 | 26 | def get_age(self, date): 27 | date = self._str_date(date) 28 | return self.calculator.calculate_age(date) 29 | 30 | 31 | class AgeableDate(datetime.date): 32 | def split(self, char): 33 | return self.year, self.month, self.day 34 | -------------------------------------------------------------------------------- /Chapter11/car_flyweight.py: -------------------------------------------------------------------------------- 1 | import weakref 2 | 3 | 4 | class CarModel: 5 | _models = weakref.WeakValueDictionary() 6 | 7 | def __new__(cls, model_name, *args, **kwargs): 8 | model = cls._models.get(model_name) 9 | if not model: 10 | model = super().__new__(cls) 11 | cls._models[model_name] = model 12 | 13 | return model 14 | 15 | def __init__( 16 | self, 17 | model_name, 18 | air=False, 19 | tilt=False, 20 | cruise_control=False, 21 | power_locks=False, 22 | alloy_wheels=False, 23 | usb_charger=False, 24 | ): 25 | if not hasattr(self, "initted"): 26 | self.model_name = model_name 27 | self.air = air 28 | self.tilt = tilt 29 | self.cruise_control = cruise_control 30 | self.power_locks = power_locks 31 | self.alloy_wheels = alloy_wheels 32 | self.usb_charger = usb_charger 33 | self.initted = True 34 | 35 | def check_serial(self, serial_number): 36 | print( 37 | "Sorry, we are unable to check " 38 | "the serial number {0} on the {1} " 39 | "at this time".format(serial_number, self.model_name) 40 | ) 41 | 42 | 43 | class Car: 44 | def __init__(self, model, color, serial): 45 | self.model = model 46 | self.color = color 47 | self.serial = serial 48 | 49 | def check_serial(self): 50 | return self.model.check_serial(self.serial) 51 | -------------------------------------------------------------------------------- /Chapter11/email_facade.py: -------------------------------------------------------------------------------- 1 | import smtplib 2 | import imaplib 3 | 4 | 5 | class EmailFacade: 6 | def __init__(self, host, username, password): 7 | self.host = host 8 | self.username = username 9 | self.password = password 10 | 11 | def send_email(self, to_email, subject, message): 12 | if not "@" in self.username: 13 | from_email = "{0}@{1}".format(self.username, self.host) 14 | else: 15 | from_email = self.username 16 | message = ( 17 | "From: {0}\r\n" "To: {1}\r\n" "Subject: {2}\r\n\r\n{3}" 18 | ).format(from_email, to_email, subject, message) 19 | 20 | smtp = smtplib.SMTP(self.host) 21 | smtp.login(self.username, self.password) 22 | smtp.sendmail(from_email, [to_email], message) 23 | 24 | def get_inbox(self): 25 | mailbox = imaplib.IMAP4(self.host) 26 | mailbox.login( 27 | bytes(self.username, "utf8"), bytes(self.password, "utf8") 28 | ) 29 | mailbox.select() 30 | x, data = mailbox.search(None, "ALL") 31 | messages = [] 32 | for num in data[0].split(): 33 | x, message = mailbox.fetch(num, "(RFC822)") 34 | messages.append(message[0][1]) 35 | return messages 36 | -------------------------------------------------------------------------------- /Chapter11/folder_composite.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 | 15 | class Folder(Component): 16 | def __init__(self, name): 17 | super().__init__(name) 18 | self.children = {} 19 | 20 | def add_child(self, child): 21 | child.parent = self 22 | self.children[child.name] = child 23 | 24 | def copy(self, new_path): 25 | pass 26 | 27 | 28 | class File(Component): 29 | def __init__(self, name, contents): 30 | super().__init__(name) 31 | self.contents = contents 32 | 33 | def copy(self, new_path): 34 | pass 35 | 36 | 37 | root = Folder("") 38 | 39 | 40 | def get_path(path): 41 | names = path.split("/")[1:] 42 | node = root 43 | for name in names: 44 | node = node.children[name] 45 | return node 46 | -------------------------------------------------------------------------------- /Chapter11/formatter_factory.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 | 10 | class USADateFormatter: 11 | def format_date(self, y, m, d): 12 | y, m, d = (str(x) for x in (y, m, d)) 13 | y = "20" + y if len(y) == 2 else y 14 | m = "0" + m if len(m) == 1 else m 15 | d = "0" + d if len(d) == 1 else d 16 | return "{0}-{1}-{2}".format(m, d, y) 17 | 18 | 19 | class FranceCurrencyFormatter: 20 | def format_currency(self, base, cents): 21 | base, cents = (str(x) for x in (base, cents)) 22 | if len(cents) == 0: 23 | cents = "00" 24 | elif len(cents) == 1: 25 | cents = "0" + cents 26 | 27 | digits = [] 28 | for i, c in enumerate(reversed(base)): 29 | if i and not i % 3: 30 | digits.append(" ") 31 | digits.append(c) 32 | base = "".join(reversed(digits)) 33 | return "{0}€{1}".format(base, cents) 34 | 35 | 36 | class USACurrencyFormatter: 37 | def format_currency(self, base, cents): 38 | base, cents = (str(x) for x in (base, cents)) 39 | if len(cents) == 0: 40 | cents = "00" 41 | elif len(cents) == 1: 42 | cents = "0" + cents 43 | digits = [] 44 | for i, c in enumerate(reversed(base)): 45 | if i and not i % 3: 46 | digits.append(",") 47 | digits.append(c) 48 | base = "".join(reversed(digits)) 49 | return "${0}.{1}".format(base, cents) 50 | 51 | 52 | class USAFormatterFactory: 53 | def create_date_formatter(self): 54 | return USADateFormatter() 55 | 56 | def create_currency_formatter(self): 57 | return USACurrencyFormatter() 58 | 59 | 60 | class FranceFormatterFactory: 61 | def create_date_formatter(self): 62 | return FranceDateFormatter() 63 | 64 | def create_currency_formatter(self): 65 | return FranceCurrencyFormatter() 66 | 67 | 68 | country_code = "US" 69 | factory_map = {"US": USAFormatterFactory, "FR": FranceFormatterFactory} 70 | formatter_factory = factory_map.get(country_code)() 71 | -------------------------------------------------------------------------------- /Chapter11/pythonic_window_command.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | class Window: 5 | def exit(self): 6 | sys.exit(0) 7 | 8 | 9 | class MenuItem: 10 | def click(self): 11 | self.command() 12 | 13 | 14 | window = Window() 15 | menu_item = MenuItem() 16 | menu_item.command = window.exit 17 | 18 | 19 | class Document: 20 | def __init__(self, filename): 21 | self.filename = filename 22 | self.contents = "This file cannot be modified" 23 | 24 | def save(self): 25 | with open(self.filename, "w") as file: 26 | file.write(self.contents) 27 | 28 | 29 | class KeyboardShortcut: 30 | def keypress(self): 31 | self.command() 32 | 33 | 34 | class SaveCommand: 35 | def __init__(self, document): 36 | self.document = document 37 | 38 | def __call__(self): 39 | self.document.save() 40 | 41 | 42 | document = Document("a_file.txt") 43 | shortcut = KeyboardShortcut() 44 | save_command = SaveCommand(document) 45 | shortcut.command = save_command 46 | -------------------------------------------------------------------------------- /Chapter11/window_command.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | class Window: 5 | def exit(self): 6 | sys.exit(0) 7 | 8 | 9 | class Document: 10 | def __init__(self, filename): 11 | self.filename = filename 12 | self.contents = "This file cannot be modified" 13 | 14 | def save(self): 15 | with open(self.filename, "w") as file: 16 | file.write(self.contents) 17 | 18 | 19 | class ToolbarButton: 20 | def __init__(self, name, iconname): 21 | self.name = name 22 | self.iconname = iconname 23 | 24 | def click(self): 25 | self.command.execute() 26 | 27 | 28 | class MenuItem: 29 | def __init__(self, menu_name, menuitem_name): 30 | self.menu = menu_name 31 | self.item = menuitem_name 32 | 33 | def click(self): 34 | self.command.execute() 35 | 36 | 37 | class KeyboardShortcut: 38 | def __init__(self, key, modifier): 39 | self.key = key 40 | self.modifier = modifier 41 | 42 | def keypress(self): 43 | self.command.execute() 44 | 45 | 46 | class SaveCommand: 47 | def __init__(self, document): 48 | self.document = document 49 | 50 | def execute(self): 51 | self.document.save() 52 | 53 | 54 | class ExitCommand: 55 | def __init__(self, window): 56 | self.window = window 57 | 58 | def execute(self): 59 | self.window.exit() 60 | 61 | 62 | window = Window() 63 | document = Document("a_document.txt") 64 | save = SaveCommand(document) 65 | exit = ExitCommand(window) 66 | 67 | save_button = ToolbarButton("save", "save.png") 68 | save_button.command = save 69 | save_keystroke = KeyboardShortcut("s", "ctrl") 70 | save_keystroke.command = save 71 | exit_menu = MenuItem("File", "Exit") 72 | exit_menu.command = exit 73 | -------------------------------------------------------------------------------- /Chapter12/Case Study_ Vigenère cipher/test_vigenere.py: -------------------------------------------------------------------------------- 1 | from vigenere_cipher import ( 2 | VigenereCipher, 3 | combine_character, 4 | separate_character, 5 | ) 6 | 7 | 8 | def test_encode(): 9 | cipher = VigenereCipher("TRAIN") 10 | encoded = cipher.encode("ENCODEDINPYTHON") 11 | assert encoded == "XECWQXUIVCRKHWA" 12 | 13 | 14 | def test_encode_character(): 15 | cipher = VigenereCipher("TRAIN") 16 | encoded = cipher.encode("E") 17 | assert encoded == "X" 18 | 19 | 20 | def test_encode_spaces(): 21 | cipher = VigenereCipher("TRAIN") 22 | encoded = cipher.encode("ENCODED IN PYTHON") 23 | assert encoded == "XECWQXUIVCRKHWA" 24 | 25 | 26 | def test_encode_lowercase(): 27 | cipher = VigenereCipher("TRain") 28 | encoded = cipher.encode("encoded in Python") 29 | assert encoded == "XECWQXUIVCRKHWA" 30 | 31 | 32 | def test_combine_character(): 33 | assert combine_character("E", "T") == "X" 34 | assert combine_character("N", "R") == "E" 35 | 36 | 37 | def test_extend_keyword(): 38 | cipher = VigenereCipher("TRAIN") 39 | extended = cipher.extend_keyword(16) 40 | assert extended == "TRAINTRAINTRAINT" 41 | extended = cipher.extend_keyword(15) 42 | assert extended == "TRAINTRAINTRAIN" 43 | 44 | 45 | def test_separate_character(): 46 | assert separate_character("X", "T") == "E" 47 | assert separate_character("E", "R") == "N" 48 | 49 | 50 | def test_decode(): 51 | cipher = VigenereCipher("TRAIN") 52 | decoded = cipher.decode("XECWQXUIVCRKHWA") 53 | assert decoded == "ENCODEDINPYTHON" 54 | -------------------------------------------------------------------------------- /Chapter12/Case Study_ Vigenère cipher/vigenere_cipher.py: -------------------------------------------------------------------------------- 1 | class VigenereCipher: 2 | def __init__(self, keyword): 3 | self.keyword = keyword 4 | 5 | def _code(self, text, combine_func): 6 | text = text.replace(" ", "").upper() 7 | combined = [] 8 | keyword = self.extend_keyword(len(text)) 9 | for p, k in zip(text, keyword): 10 | combined.append(combine_func(p, k)) 11 | return "".join(combined) 12 | 13 | def encode(self, plaintext): 14 | return self._code(plaintext, combine_character) 15 | 16 | def decode(self, ciphertext): 17 | return self._code(ciphertext, separate_character) 18 | 19 | def extend_keyword(self, number): 20 | repeats = number // len(self.keyword) + 1 21 | return (self.keyword * repeats)[:number] 22 | 23 | 24 | def combine_character(plain, keyword): 25 | plain = plain.upper() 26 | keyword = keyword.upper() 27 | plain_num = ord(plain) - ord("A") 28 | keyword_num = ord(keyword) - ord("A") 29 | return chr(ord("A") + (plain_num + keyword_num) % 26) 30 | 31 | 32 | def separate_character(cypher, keyword): 33 | cypher = cypher.upper() 34 | keyword = keyword.upper() 35 | cypher_num = ord(cypher) - ord("A") 36 | keyword_num = ord(keyword) - ord("A") 37 | return chr(ord("A") + (cypher_num - keyword_num) % 26) 38 | 39 | -------------------------------------------------------------------------------- /Chapter12/average_raises.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | def average(seq): 5 | return sum(seq) / len(seq) 6 | 7 | 8 | class TestAverage(unittest.TestCase): 9 | def test_zero(self): 10 | self.assertRaises(ZeroDivisionError, average, []) 11 | 12 | def test_with_zero(self): 13 | with self.assertRaises(ZeroDivisionError): 14 | average([]) 15 | 16 | 17 | if __name__ == "__main__": 18 | unittest.main() 19 | -------------------------------------------------------------------------------- /Chapter12/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 | while True: 9 | client, address = s.accept() 10 | data = client.recv(1024) 11 | client.send(data) 12 | client.close() 13 | -------------------------------------------------------------------------------- /Chapter12/first_unittest.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class CheckNumbers(unittest.TestCase): 5 | def test_int_float(self): 6 | self.assertEqual(1, 1.0) 7 | 8 | def test_str_float(self): 9 | self.assertEqual(1, "1") 10 | 11 | 12 | if __name__ == "__main__": 13 | unittest.main() 14 | -------------------------------------------------------------------------------- /Chapter12/flight_status_redis.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import redis 3 | 4 | 5 | class FlightStatusTracker: 6 | ALLOWED_STATUSES = {"CANCELLED", "DELAYED", "ON TIME"} 7 | 8 | def __init__(self): 9 | self.redis = redis.StrictRedis() 10 | 11 | def change_status(self, flight, status): 12 | status = status.upper() 13 | if status not in self.ALLOWED_STATUSES: 14 | raise ValueError("{} is not a valid status".format(status)) 15 | 16 | key = "flightno:{}".format(flight) 17 | value = "{}|{}".format( 18 | datetime.datetime.now().isoformat(), status 19 | ) 20 | self.redis.set(key, value) 21 | -------------------------------------------------------------------------------- /Chapter12/stats.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | 4 | class StatsList(list): 5 | def mean(self): 6 | return sum(self) / len(self) 7 | 8 | def median(self): 9 | if len(self) % 2: 10 | return self[int(len(self) / 2)] 11 | else: 12 | idx = int(len(self) / 2) 13 | return (self[idx] + self[idx - 1]) / 2 14 | 15 | def mode(self): 16 | freqs = defaultdict(int) 17 | for item in self: 18 | freqs[item] += 1 19 | mode_freq = max(freqs.values()) 20 | modes = [] 21 | for item, value in freqs.items(): 22 | if value == mode_freq: 23 | modes.append(item) 24 | return modes 25 | -------------------------------------------------------------------------------- /Chapter12/stats_list_setup.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | 4 | class StatsList(list): 5 | def mean(self): 6 | return sum(self) / len(self) 7 | 8 | def median(self): 9 | if len(self) % 2: 10 | return self[int(len(self) / 2)] 11 | else: 12 | idx = int(len(self) / 2) 13 | return (self[idx] + self[idx - 1]) / 2 14 | 15 | def mode(self): 16 | freqs = defaultdict(int) 17 | for item in self: 18 | freqs[item] += 1 19 | mode_freq = max(freqs.values()) 20 | modes = [] 21 | for item, value in freqs.items(): 22 | if value == mode_freq: 23 | modes.append(item) 24 | return modes 25 | -------------------------------------------------------------------------------- /Chapter12/test_echo_server.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import socket 3 | import time 4 | import pytest 5 | 6 | 7 | @pytest.fixture(scope="session") 8 | def echoserver(): 9 | print("loading server") 10 | p = subprocess.Popen(["python3", "echo_server.py"]) 11 | time.sleep(1) 12 | yield p 13 | p.terminate() 14 | 15 | 16 | @pytest.fixture 17 | def clientsocket(request): 18 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 19 | s.connect(("localhost", 1028)) 20 | yield s 21 | s.close() 22 | 23 | 24 | def test_echo(echoserver, clientsocket): 25 | clientsocket.send(b"abc") 26 | assert clientsocket.recv(3) == b"abc" 27 | 28 | 29 | def test_echo2(echoserver, clientsocket): 30 | clientsocket.send(b"def") 31 | assert clientsocket.recv(3) == b"def" 32 | 33 | -------------------------------------------------------------------------------- /Chapter12/test_flight_status.py: -------------------------------------------------------------------------------- 1 | from flight_status_redis import FlightStatusTracker 2 | from unittest.mock import Mock, patch 3 | import datetime 4 | import pytest 5 | 6 | 7 | @pytest.fixture 8 | def tracker(): 9 | return FlightStatusTracker() 10 | 11 | 12 | def test_mock_method(tracker): 13 | tracker.redis.set = Mock() 14 | with pytest.raises(ValueError) as ex: 15 | tracker.change_status("AC101", "lost") 16 | assert ex.value.args[0] == "LOST is not a valid status" 17 | assert tracker.redis.set.call_count == 0 18 | 19 | 20 | def test_patch(tracker): 21 | tracker.redis.set = Mock() 22 | fake_now = datetime.datetime(2015, 4, 1) 23 | with patch("datetime.datetime") as dt: 24 | dt.now.return_value = fake_now 25 | tracker.change_status("AC102", "on time") 26 | dt.now.assert_called_once_with() 27 | tracker.redis.set.assert_called_once_with( 28 | "flightno:AC102", "2015-04-01T00:00:00|ON TIME" 29 | ) 30 | 31 | -------------------------------------------------------------------------------- /Chapter12/test_pytest_cleanup.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import tempfile 3 | import shutil 4 | import os.path 5 | 6 | 7 | @pytest.fixture 8 | def temp_dir(request): 9 | dir = tempfile.mkdtemp() 10 | print(dir) 11 | yield dir 12 | shutil.rmtree(dir) 13 | 14 | 15 | def test_osfiles(temp_dir): 16 | os.mkdir(os.path.join(temp_dir, "a")) 17 | os.mkdir(os.path.join(temp_dir, "b")) 18 | dir_contents = os.listdir(temp_dir) 19 | assert len(dir_contents) == 2 20 | assert "a" in dir_contents 21 | assert "b" in dir_contents 22 | 23 | -------------------------------------------------------------------------------- /Chapter12/test_pytest_setups.py: -------------------------------------------------------------------------------- 1 | def setup_module(module): 2 | print("setting up MODULE {0}".format(module.__name__)) 3 | 4 | 5 | def teardown_module(module): 6 | print("tearing down MODULE {0}".format(module.__name__)) 7 | 8 | 9 | def test_a_function(): 10 | print("RUNNING TEST FUNCTION") 11 | 12 | 13 | class BaseTest: 14 | def setup_class(cls): 15 | print("setting up CLASS {0}".format(cls.__name__)) 16 | 17 | def teardown_class(cls): 18 | print("tearing down CLASS {0}\n".format(cls.__name__)) 19 | 20 | def setup_method(self, method): 21 | print("setting up METHOD {0}".format(method.__name__)) 22 | 23 | def teardown_method(self, method): 24 | print("tearing down METHOD {0}".format(method.__name__)) 25 | 26 | 27 | class TestClass1(BaseTest): 28 | def test_method_1(self): 29 | print("RUNNING METHOD 1-1") 30 | 31 | def test_method_2(self): 32 | print("RUNNING METHOD 1-2") 33 | 34 | 35 | class TestClass2(BaseTest): 36 | def test_method_1(self): 37 | print("RUNNING METHOD 2-1") 38 | 39 | def test_method_2(self): 40 | print("RUNNING METHOD 2-2") 41 | -------------------------------------------------------------------------------- /Chapter12/test_pytest_skipping.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pytest 3 | 4 | 5 | def test_simple_skip(): 6 | if sys.platform != "fakeos": 7 | pytest.skip("Test works only on fakeOS") 8 | 9 | fakeos.do_something_fake() 10 | assert fakeos.did_not_happen 11 | 12 | 13 | @pytest.mark.skipif("sys.version_info <= (3,0)") 14 | def test_python3(): 15 | assert b"hello".decode() == "hello" 16 | -------------------------------------------------------------------------------- /Chapter12/test_pytest_stats.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from stats import StatsList 3 | 4 | 5 | @pytest.fixture 6 | def valid_stats(): 7 | return StatsList([1, 2, 2, 3, 3, 4]) 8 | 9 | 10 | def test_mean(valid_stats): 11 | assert valid_stats.mean() == 2.5 12 | 13 | 14 | def test_median(valid_stats): 15 | assert valid_stats.median() == 2.5 16 | valid_stats.append(4) 17 | assert valid_stats.median() == 3 18 | 19 | 20 | def test_mode(valid_stats): 21 | assert valid_stats.mode() == [2, 3] 22 | valid_stats.remove(2) 23 | assert valid_stats.mode() == [3] 24 | -------------------------------------------------------------------------------- /Chapter12/test_skipping.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sys 3 | 4 | 5 | class SkipTests(unittest.TestCase): 6 | @unittest.expectedFailure 7 | def test_fails(self): 8 | self.assertEqual(False, True) 9 | 10 | @unittest.skip("Test is useless") 11 | def test_skip(self): 12 | self.assertEqual(False, True) 13 | 14 | @unittest.skipIf(sys.version_info.minor == 7, "broken on 3.7") 15 | def test_skipif(self): 16 | self.assertEqual(False, True) 17 | 18 | @unittest.skipUnless( 19 | sys.platform.startswith("linux"), "broken unless on linux" 20 | ) 21 | def test_skipunless(self): 22 | self.assertEqual(False, True) 23 | 24 | 25 | if __name__ == "__main__": 26 | unittest.main() 27 | -------------------------------------------------------------------------------- /Chapter12/test_statslist_setup.py: -------------------------------------------------------------------------------- 1 | from stats import StatsList 2 | import unittest 3 | 4 | 5 | class TestValidInputs(unittest.TestCase): 6 | def setUp(self): 7 | self.stats = StatsList([1, 2, 2, 3, 3, 4]) 8 | 9 | def test_mean(self): 10 | self.assertEqual(self.stats.mean(), 2.5) 11 | 12 | def test_median(self): 13 | self.assertEqual(self.stats.median(), 2.5) 14 | self.stats.append(4) 15 | self.assertEqual(self.stats.median(), 3) 16 | 17 | def test_mode(self): 18 | self.assertEqual(self.stats.mode(), [2, 3]) 19 | self.stats.remove(2) 20 | self.assertEqual(self.stats.mode(), [3]) 21 | 22 | 23 | if __name__ == "__main__": 24 | unittest.main() 25 | -------------------------------------------------------------------------------- /Chapter12/test_with_pytest.py: -------------------------------------------------------------------------------- 1 | def test_int_float(): 2 | assert 1 == 1.0 3 | 4 | 5 | class TestNumbers: 6 | def test_int_float(self): 7 | assert 1 == 1.0 8 | 9 | def test_int_str(self): 10 | assert 1 == "1" 11 | -------------------------------------------------------------------------------- /Chapter13/Case Study_ Compression/bricks.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Third-Edition/6dc21b5e65465712c9923aabc9b713d327412258/Chapter13/Case Study_ Compression/bricks.bmp -------------------------------------------------------------------------------- /Chapter13/Case Study_ Compression/compiling1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Third-Edition/6dc21b5e65465712c9923aabc9b713d327412258/Chapter13/Case Study_ Compression/compiling1.bmp -------------------------------------------------------------------------------- /Chapter13/Case Study_ Compression/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 | 9 | def compress_row(row): 10 | compressed = bytearray() 11 | chunks = split_bits(row, 127) 12 | for chunk in chunks: 13 | compressed.extend(compress_chunk(chunk)) 14 | return compressed 15 | 16 | 17 | def compress_chunk(chunk): 18 | compressed = bytearray() 19 | count = 1 20 | last = chunk[0] 21 | for bit in chunk[1:]: 22 | if bit != last: 23 | compressed.append(count | (128 * last)) 24 | count = 0 25 | last = bit 26 | count += 1 27 | compressed.append(count | (128 * last)) 28 | return compressed 29 | 30 | 31 | def split_bits(bits, width): 32 | for i in range(0, len(bits), width): 33 | yield bits[i : i + width] 34 | 35 | 36 | def compress_in_executor(executor, bits, width): 37 | row_compressors = [] 38 | for row in split_bits(bits, width): 39 | compressor = executor.submit(compress_row, row) 40 | row_compressors.append(compressor) 41 | 42 | compressed = bytearray() 43 | for compressor in row_compressors: 44 | compressed.extend(compressor.result()) 45 | return compressed 46 | 47 | 48 | def compress_image(in_filename, out_filename, executor=None): 49 | executor = executor if executor else ThreadPoolExecutor(4) 50 | with Image.open(in_filename) as image: 51 | bits = bitarray(image.convert("1").getdata()) 52 | width, height = image.size 53 | 54 | compressed = compress_in_executor(executor, bits, width) 55 | 56 | with open(out_filename, "wb") as file: 57 | file.write(width.to_bytes(2, "little")) 58 | file.write(height.to_bytes(2, "little")) 59 | file.write(compressed) 60 | 61 | 62 | def compress_dir(in_dir, out_dir): 63 | if not out_dir.exists(): 64 | out_dir.mkdir() 65 | 66 | executor = ThreadPoolExecutor(4) 67 | futures = [] 68 | for file in (f for f in in_dir.iterdir() if f.suffix == ".bmp"): 69 | out_file = (out_dir / file.name).with_suffix(".rle") 70 | futures.append( 71 | executor.submit(compress_image, str(file), str(out_file)) 72 | ) 73 | for future in futures: 74 | future.result() 75 | 76 | 77 | def single_image_main(): 78 | in_filename, out_filename = sys.argv[1:3] 79 | executor = ThreadPoolExecutor(4) 80 | # executor = ProcessPoolExecutor() 81 | compress_image(in_filename, out_filename, executor) 82 | 83 | 84 | def dir_images_main(): 85 | in_dir, out_dir = (Path(p) for p in sys.argv[1:3]) 86 | compress_dir(in_dir, out_dir) 87 | 88 | 89 | if __name__ == "__main__": 90 | dir_images_main() 91 | # single_image_main() 92 | -------------------------------------------------------------------------------- /Chapter13/Case Study_ Compression/decompress_to_bmp.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | import sys 3 | 4 | 5 | def decompress(width, height, bytes): 6 | image = Image.new("1", (width, height)) 7 | 8 | col = 0 9 | row = 0 10 | for byte in bytes: 11 | color = (byte & 128) >> 7 12 | count = byte & ~128 13 | for i in range(count): 14 | image.putpixel((row, col), color) 15 | row += 1 16 | if not row % width: 17 | col += 1 18 | row = 0 19 | return image 20 | 21 | 22 | with open(sys.argv[1], "rb") as file: 23 | width = int.from_bytes(file.read(2), "little") 24 | height = int.from_bytes(file.read(2), "little") 25 | 26 | image = decompress(width, height, file.read()) 27 | image.save(sys.argv[2], "bmp") 28 | -------------------------------------------------------------------------------- /Chapter13/Case Study_ Compression/exploits_of_a_mom.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Third-Edition/6dc21b5e65465712c9923aabc9b713d327412258/Chapter13/Case Study_ Compression/exploits_of_a_mom.bmp -------------------------------------------------------------------------------- /Chapter13/Case Study_ Compression/python.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Third-Edition/6dc21b5e65465712c9923aabc9b713d327412258/Chapter13/Case Study_ Compression/python.bmp -------------------------------------------------------------------------------- /Chapter13/Case Study_ Compression/results/bricks.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Third-Edition/6dc21b5e65465712c9923aabc9b713d327412258/Chapter13/Case Study_ Compression/results/bricks.bmp -------------------------------------------------------------------------------- /Chapter13/Case Study_ Compression/results/bricks.rle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Third-Edition/6dc21b5e65465712c9923aabc9b713d327412258/Chapter13/Case Study_ Compression/results/bricks.rle -------------------------------------------------------------------------------- /Chapter13/Case Study_ Compression/results/compiling1.rle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Third-Edition/6dc21b5e65465712c9923aabc9b713d327412258/Chapter13/Case Study_ Compression/results/compiling1.rle -------------------------------------------------------------------------------- /Chapter13/Case Study_ Compression/results/exploits_of_a_mom.rle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Third-Edition/6dc21b5e65465712c9923aabc9b713d327412258/Chapter13/Case Study_ Compression/results/exploits_of_a_mom.rle -------------------------------------------------------------------------------- /Chapter13/Case Study_ Compression/results/python.rle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Third-Edition/6dc21b5e65465712c9923aabc9b713d327412258/Chapter13/Case Study_ Compression/results/python.rle -------------------------------------------------------------------------------- /Chapter13/Case Study_ Compression/results/row.rle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Third-Edition/6dc21b5e65465712c9923aabc9b713d327412258/Chapter13/Case Study_ Compression/results/row.rle -------------------------------------------------------------------------------- /Chapter13/Case Study_ Compression/results/sandwich.rle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Third-Edition/6dc21b5e65465712c9923aabc9b713d327412258/Chapter13/Case Study_ Compression/results/sandwich.rle -------------------------------------------------------------------------------- /Chapter13/Case Study_ Compression/row.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Third-Edition/6dc21b5e65465712c9923aabc9b713d327412258/Chapter13/Case Study_ Compression/row.bmp -------------------------------------------------------------------------------- /Chapter13/Case Study_ Compression/sandwich.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-3-Object-Oriented-Programming-Third-Edition/6dc21b5e65465712c9923aabc9b713d327412258/Chapter13/Case Study_ Compression/sandwich.bmp -------------------------------------------------------------------------------- /Chapter13/async_sleep.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | 4 | 5 | async def random_sleep(counter): 6 | delay = random.random() * 5 7 | print("{} sleeps for {:.2f} seconds".format(counter, delay)) 8 | await asyncio.sleep(delay) 9 | print("{} awakens".format(counter)) 10 | 11 | 12 | async def five_sleepers(): 13 | print("Creating five tasks") 14 | tasks = [asyncio.create_task(random_sleep(i)) for i in range(5)] 15 | print("Sleeping after starting five tasks") 16 | await asyncio.sleep(2) 17 | print("Waking and waiting for five tasks") 18 | await asyncio.gather(*tasks) 19 | 20 | 21 | asyncio.get_event_loop().run_until_complete(five_sleepers()) 22 | print("Done five tasks") 23 | -------------------------------------------------------------------------------- /Chapter13/basic_thread.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | 3 | 4 | class InputReader(Thread): 5 | def run(self): 6 | self.line_of_text = input() 7 | 8 | 9 | print("Enter some text and press enter: ") 10 | thread = InputReader() 11 | thread.start() 12 | 13 | count = result = 1 14 | while thread.is_alive(): 15 | result = count * count 16 | count += 1 17 | 18 | print("calculated squares up to {0} * {0} = {1}".format(count, result)) 19 | print("while you typed '{}'".format(thread.line_of_text)) 20 | -------------------------------------------------------------------------------- /Chapter13/dns_server.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 | b"dataquest.io.": "104.20.20.199", 9 | } 10 | 11 | 12 | def lookup_dns(data): 13 | domain = b"" 14 | pointer, part_length = 13, data[12] 15 | while part_length: 16 | domain += data[pointer : pointer + part_length] + b"." 17 | pointer += part_length + 1 18 | part_length = data[pointer - 1] 19 | 20 | ip = ip_map.get(domain, "127.0.0.1") 21 | 22 | return domain, ip 23 | 24 | 25 | def create_response(data, ip): 26 | ba = bytearray 27 | packet = ba(data[:2]) + ba([129, 128]) + data[4:6] * 2 28 | packet += ba(4) + data[12:] 29 | packet += ba([192, 12, 0, 1, 0, 1, 0, 0, 0, 60, 0, 4]) 30 | for x in ip.split("."): 31 | packet.append(int(x)) 32 | return packet 33 | 34 | 35 | class DNSProtocol(asyncio.DatagramProtocol): 36 | def connection_made(self, transport): 37 | self.transport = transport 38 | 39 | def datagram_received(self, data, addr): 40 | print("Received request from {}".format(addr[0])) 41 | domain, ip = lookup_dns(data) 42 | print( 43 | "Sending IP {} for {} to {}".format( 44 | domain.decode(), ip, addr[0] 45 | ) 46 | ) 47 | self.transport.sendto(create_response(data, ip), addr) 48 | 49 | 50 | loop = asyncio.get_event_loop() 51 | transport, protocol = loop.run_until_complete( 52 | loop.create_datagram_endpoint( 53 | DNSProtocol, local_addr=("127.0.0.1", 4343) 54 | ) 55 | ) 56 | print("DNS Server running") 57 | 58 | with suppress(KeyboardInterrupt): 59 | loop.run_forever() 60 | transport.close() 61 | loop.close() 62 | -------------------------------------------------------------------------------- /Chapter13/muchcpu.py: -------------------------------------------------------------------------------- 1 | from multiprocessing import Process, cpu_count 2 | import time 3 | import os 4 | 5 | from threading import Thread 6 | 7 | 8 | class MuchCPU(Thread): 9 | def run(self): 10 | print(os.getpid()) 11 | for i in range(200000000): 12 | pass 13 | 14 | 15 | if __name__ == "__main__": 16 | procs = [MuchCPU() for f in range(cpu_count())] 17 | t = time.time() 18 | for p in procs: 19 | p.start() 20 | for p in procs: 21 | p.join() 22 | print("work took {} seconds".format(time.time() - t)) 23 | 24 | -------------------------------------------------------------------------------- /Chapter13/prime_factor.py: -------------------------------------------------------------------------------- 1 | import random 2 | from multiprocessing.pool import Pool 3 | 4 | 5 | def prime_factor(value): 6 | factors = [] 7 | for divisor in range(2, value - 1): 8 | quotient, remainder = divmod(value, divisor) 9 | if not remainder: 10 | factors.extend(prime_factor(divisor)) 11 | factors.extend(prime_factor(quotient)) 12 | break 13 | else: 14 | factors = [value] 15 | return factors 16 | 17 | 18 | if __name__ == "__main__": 19 | pool = Pool() 20 | 21 | to_factor = [random.randint(100000, 50000000) for i in range(20)] 22 | results = pool.map(prime_factor, to_factor) 23 | for value, factors in zip(to_factor, results): 24 | print("The factors of {} are {}".format(value, factors)) 25 | -------------------------------------------------------------------------------- /Chapter13/searching_with_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 | 7 | def find_files(path, query_string): 8 | subdirs = [] 9 | for p in path.iterdir(): 10 | full_path = str(p.absolute()) 11 | if p.is_dir() and not p.is_symlink(): 12 | subdirs.append(p) 13 | if query_string in full_path: 14 | print(full_path) 15 | 16 | return subdirs 17 | 18 | 19 | query = ".py" 20 | futures = deque() 21 | basedir = Path(pathsep).absolute() 22 | 23 | with ThreadPoolExecutor(max_workers=10) as executor: 24 | futures.append(executor.submit(find_files, basedir, query)) 25 | while futures: 26 | future = futures.popleft() 27 | if future.exception(): 28 | continue 29 | elif future.done(): 30 | subdirs = future.result() 31 | for subdir in subdirs: 32 | futures.append( 33 | executor.submit(find_files, subdir, query) 34 | ) 35 | else: 36 | futures.append(future) 37 | -------------------------------------------------------------------------------- /Chapter13/searching_with_queues.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 | 12 | if __name__ == "__main__": 13 | from multiprocessing import Process, Queue, cpu_count 14 | from path import path 15 | 16 | cpus = cpu_count() 17 | pathnames = [f for f in path(".").listdir() if f.isfile()] 18 | paths = [pathnames[i::cpus] for i in range(cpus)] 19 | query_queues = [Queue() for p in range(cpus)] 20 | results_queue = Queue() 21 | 22 | search_procs = [ 23 | Process(target=search, args=(p, q, results_queue)) 24 | for p, q in zip(paths, query_queues) 25 | ] 26 | for proc in search_procs: 27 | proc.start() 28 | 29 | for q in query_queues: 30 | q.put("def") 31 | q.put(None) # Signal process termination 32 | 33 | for i in range(cpus): 34 | for match in results_queue.get(): 35 | print(match) 36 | for proc in search_procs: 37 | proc.join() 38 | 39 | -------------------------------------------------------------------------------- /Chapter13/sort_client.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | import json 4 | 5 | 6 | async def remote_sort(): 7 | reader, writer = await 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 = await 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 | 24 | loop = asyncio.get_event_loop() 25 | loop.run_until_complete(remote_sort()) 26 | loop.close() 27 | -------------------------------------------------------------------------------- /Chapter13/sort_service.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | from concurrent.futures import ProcessPoolExecutor 4 | 5 | 6 | def sort_in_process(data): 7 | nums = json.loads(data.decode()) 8 | curr = 1 9 | while curr < len(nums): 10 | if nums[curr] >= nums[curr - 1]: 11 | curr += 1 12 | else: 13 | nums[curr], nums[curr - 1] = nums[curr - 1], nums[curr] 14 | if curr > 1: 15 | curr -= 1 16 | 17 | return json.dumps(nums).encode() 18 | 19 | 20 | async def sort_request(reader, writer): 21 | print("Received connection") 22 | length = await reader.read(8) 23 | data = await reader.readexactly(int.from_bytes(length, "big")) 24 | result = await asyncio.get_event_loop().run_in_executor( 25 | None, sort_in_process, data 26 | ) 27 | print("Sorted list") 28 | writer.write(result) 29 | writer.close() 30 | print("Connection closed") 31 | 32 | 33 | loop = asyncio.get_event_loop() 34 | loop.set_default_executor(ProcessPoolExecutor()) 35 | server = loop.run_until_complete( 36 | asyncio.start_server(sort_request, "127.0.0.1", 2015) 37 | ) 38 | print("Sort Service running") 39 | 40 | loop.run_forever() 41 | server.close() 42 | loop.run_until_complete(server.wait_closed()) 43 | loop.close() 44 | -------------------------------------------------------------------------------- /Chapter13/weather_today.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | import time 3 | from urllib.request import urlopen 4 | from xml.etree import ElementTree 5 | 6 | 7 | CITIES = { 8 | "Charlottetown": ("PE", "s0000583"), 9 | "Edmonton": ("AB", "s0000045"), 10 | "Fredericton": ("NB", "s0000250"), 11 | "Halifax": ("NS", "s0000318"), 12 | "Iqaluit": ("NU", "s0000394"), 13 | "Québec City": ("QC", "s0000620"), 14 | "Regina": ("SK", "s0000788"), 15 | "St. John's": ("NL", "s0000280"), 16 | "Toronto": ("ON", "s0000458"), 17 | "Victoria": ("BC", "s0000775"), 18 | "Whitehorse": ("YT", "s0000825"), 19 | "Winnipeg": ("MB", "s0000193"), 20 | "Yellowknife": ("NT", "s0000366"), 21 | } 22 | 23 | 24 | class TempGetter(Thread): 25 | def __init__(self, city): 26 | super().__init__() 27 | self.city = city 28 | self.province, self.code = CITIES[self.city] 29 | 30 | def run(self): 31 | url = ( 32 | "https://dd.weather.gc.ca/citypage_weather/xml/" 33 | f"{self.province}/{self.code}_e.xml" 34 | ) 35 | with urlopen(url) as stream: 36 | xml = ElementTree.parse(stream) 37 | self.temperature = xml.find( 38 | "currentConditions/temperature" 39 | ).text 40 | 41 | 42 | threads = [TempGetter(c) for c in CITIES] 43 | start = time.time() 44 | for thread in threads: 45 | thread.start() 46 | 47 | for thread in threads: 48 | thread.join() 49 | 50 | for thread in threads: 51 | print(f"it is {thread.temperature}°C in {thread.city}") 52 | print( 53 | "Got {} temps in {} seconds".format( 54 | len(threads), time.time() - start 55 | ) 56 | ) 57 | 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python 3 Object-Oriented Programming - Third Edition 2 | 3 | Python 3 Object-Oriented Programming - Third Edition 4 | 5 | This is the code repository for [Python 3 Object-Oriented Programming - Third Edition](https://www.packtpub.com/application-development/python-3-object-oriented-programming-third-edition?utm_source=github&utm_medium=repository&utm_campaign=9781789615852), published by Packt. 6 | 7 | **Build robust and maintainable software with object-oriented design patterns in Python 3.8** 8 | 9 | ## What is this book about? 10 | Object-oriented programming (OOP) is a popular design paradigm in which data and behaviors are encapsulated in such a way that they can be manipulated together. This third edition of Python 3 Object-Oriented Programming fully explains classes, data encapsulation, and exceptions with an emphasis on when you can use each principle to develop well-designed software. 11 | 12 | This book covers the following exciting features: 13 | * Implement objects in Python by creating classes and defining methods 14 | * Grasp common concurrency techniques and pitfalls in Python 3 15 | * Extend class functionality using inheritance 16 | * Understand when to use object-oriented features, and more importantly when not to use them 17 | * Discover what design patterns are and why they are different in Python 18 | * Uncover the simplicity of unit testing and why it's so important in Python 19 | * Explore object-oriented programming concurrently with asyncio 20 | 21 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1789615852) today! 22 | 23 | https://www.packtpub.com/ 25 | 26 | ## Instructions and Navigations 27 | All of the code is organized into folders. For example, Chapter02. 28 | 29 | The code will look like the following: 30 | ``` 31 | class Point: 32 | def __init__(self, x=0, y=0): 33 | self.move(x, y) 34 | ``` 35 | 36 | **Following is what you need for this book:** 37 | If you're new to object-oriented programming techniques, or if you have basic Python skills and wish to learn in depth how and when to correctly apply OOP in Python, this is the book for you. If you are an object-oriented programmer for other languages or seeking a leg up in the new world of Python 3.8, you too will find this book a useful introduction to Python. Previous experience with Python 3 is not necessary. 38 | 39 | With the following software and hardware list you can run all code files present in the book (Chapter 1-13). 40 | ### Software and Hardware List 41 | | Chapter | Software required | OS required | 42 | | -------- | ------------------------------------ | ----------------------------------- | 43 | | 2-13 | Python 3.5.2 and above | Windows, Mac OS X, and Linux (Any) | 44 | 45 | ### Related products 46 | * Functional Python Programming - Second Edition [[Packt]](https://www.packtpub.com/application-development/functional-python-programming-second-edition?utm_source=github&utm_medium=repository&utm_campaign=9781788627061) [[Amazon]](https://www.amazon.com/dp/1788627067) 47 | 48 | * Mastering Python Design Patterns - Second Edition [[Packt]](https://www.packtpub.com/application-development/mastering-python-design-patterns-second-edition?utm_source=github&utm_medium=repository&utm_campaign=9781788837484) [[Amazon]](https://www.amazon.com/dp/1788837487) 49 | 50 | ## Get to Know the Author 51 | **Dusty Phillips** 52 | 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 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 startups. He's currently researching privacy-preserving technology at beanstalk.network. 53 | 54 | Python 3 Object Oriented Programming was his first book. He has also written Creating Apps In Kivy, and self-published Hacking Happy, a journey to mental wellness for the technically inclined. A work of fiction is coming as well, so stay tuned! 55 | 56 | ## Other books by the author 57 | [Python 3 Object-oriented Programming - Second Edition](https://www.packtpub.com/application-development/python-3-object-oriented-programming-second-edition?utm_source=github&utm_medium=repository&utm_campaign=9781784398781) 58 | 59 | [Python 3 Object Oriented Programming](https://www.packtpub.com/application-development/python-3-object-oriented-programming?utm_source=github&utm_medium=repository&utm_campaign=9781849511261) 60 | 61 | ### Suggestions and Feedback 62 | [Click here](https://docs.google.com/forms/d/e/1FAIpQLSdy7dATC6QmEL81FIUuymZ0Wy9vH1jHkvpY57OiMeKGqib_Ow/viewform) if you have any feedback or suggestions. 63 | 64 | 65 | --------------------------------------------------------------------------------