├── .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 |My name is Dusty. This is the content of my front page. It goes below the menu.
10 |Favourite Books | 13 |
---|
Thief Of Time | 17 |
The Thief | 22 |
Snow Crash | 27 |
Lathe Of Heaven | 32 |