generated from CodeYourFuture/Module-Template
-
-
Notifications
You must be signed in to change notification settings - Fork 42
London | 25-SDC-Nov | Zohreh Kazemianpour | Sprint 5 | Prep Exercises #305
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
zohrehKazemianpour
wants to merge
7
commits into
CodeYourFuture:main
Choose a base branch
from
zohrehKazemianpour:prep-Exercise
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
9390402
Add Sprint 5 prep exercises: types, mypy, classes
zohrehKazemianpour bce9afe
Fix comment: correct JS coercion behaviour
zohrehKazemianpour 151a843
Add person2.py using dataclass
zohrehKazemianpour f05d7a1
Add generics.py exercise
zohrehKazemianpour ec96515
Add refactoring.py: practice type-guided refactoring with mypy
zohrehKazemianpour 5711e4a
Complete enum exercise: laptop OS matching system
zohrehKazemianpour 9bc35cf
Complete inheritance exercise
zohrehKazemianpour File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,3 @@ | ||
| node_modules | ||
| .venv/ | ||
| prep |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| def open_account(balances: dict, name: str, amount: int) -> None: | ||
| balances[name] = amount | ||
|
|
||
| def sum_balances(accounts: dict) -> int: | ||
| total = 0 | ||
| for name, pence in accounts.items(): | ||
| print(f"{name} had balance {pence}") | ||
| total += pence | ||
| return total | ||
|
|
||
| def format_pence_as_string(total_pence: int) -> str: | ||
| if total_pence < 100: | ||
| return f"{total_pence}p" | ||
| pounds = int(total_pence / 100) | ||
| pence = total_pence % 100 | ||
| return f"£{pounds}.{pence:02d}" | ||
|
|
||
| balances = { | ||
| "Sima": 700, | ||
| "Linn": 545, | ||
| "Georg": 831, | ||
| } | ||
|
|
||
| open_account(balances, "Tobi", 913) | ||
| open_account(balances, "Olya", 713) | ||
|
|
||
| total_pence = sum_balances(balances) | ||
| total_string = format_pence_as_string(total_pence) | ||
|
|
||
| print(f"The bank accounts total {total_string}") | ||
|
|
||
|
|
||
|
|
||
|
|
||
| # When I ran mypy I got these errors: | ||
|
|
||
| # Error 1: bank.py:24: error: Missing positional argument "amount" in call to "open_account" [call-arg] | ||
| # The function open_account expects three arguments but only two have been passed. We need to add one more argument. | ||
|
|
||
| # Error 2: Argument 1 has incompatible type "str"; expected "dict" | ||
| # By adding balances as the first argument, this will be solved as well. | ||
|
|
||
| # Error 3: Argument 2 has incompatible type "float"; expected "str" | ||
| # 9.13 is in the wrong position, and it's a float not an int. | ||
|
|
||
| # Error 4: Missing positional argument "amount" in call to "open_account" | ||
| # Same problem as Error 1 missing balances and wrong types. | ||
|
|
||
| # Error 5: bank.py:25: error: Argument 1 to "open_account" has incompatible type "str"; expected "dict[Any, Any]" | ||
| # Line 25 has two bugs: balances should be passed as the first argument, and the third argument is passed as a string which should be an int. | ||
|
|
||
| # Error 6: bank.py:28: error: Name "format_pence_as_str" is not defined [name-defined] | ||
| # Typo! Should be format_pence_as_string. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| def double(value): | ||
| return value * 2 | ||
|
|
||
|
|
||
| double("22") | ||
| print(double("22")) | ||
|
|
||
|
|
||
| # Coming from JS, I predicted that Python might behave the same way. | ||
| # Under the hood, without throwing an error, Python would concatenate | ||
| # the string "22" with itself, and the result would be "2222". | ||
|
|
||
| # correction: later on I realised JavaScript and Python behave differently JS coerces so "22" * 2 returns 44 in JS whereas Python repeats the string according to the number, so "22" * 2 returns "2222". | ||
|
|
||
|
|
||
| def double(number): | ||
| return number * 3 | ||
|
|
||
| print(double(10)) | ||
|
|
||
| # As mentioned in prep section either the name should be triple or the code should be *2 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| """ | ||
| Laptop Library System | ||
| This program helps library users find available laptops matching their | ||
| preferred operating system. It demonstrates enum usage, type safety, | ||
| and input validation. | ||
| """ | ||
|
|
||
|
|
||
| from dataclasses import dataclass | ||
| from enum import Enum | ||
| from typing import List | ||
| import sys | ||
|
|
||
|
|
||
| # -------------------------------------------------------------------- | ||
| # ENUM DEFINITIONS | ||
| # -------------------------------------------------------------------- | ||
| """ | ||
| Represents valid operating systems for laptops. | ||
| Using enums prevents string comparison issues (case, typos, spaces). | ||
| """ | ||
|
|
||
| class OperatingSystem(Enum): | ||
| UBUNTU = "Ubuntu" | ||
| MACOS = "macOS" | ||
| ARCH = "Arch Linux" | ||
|
|
||
| # -------------------------------------------------------------------- | ||
| # DATA CLASSES | ||
| # -------------------------------------------------------------------- | ||
| #Represents a library user with their preferences. | ||
|
|
||
| @dataclass(frozen=True) | ||
| class Person: | ||
| name: str | ||
| age: int | ||
| preferred_operating_systems: OperatingSystem | ||
|
|
||
|
|
||
| #Represents a laptop available in the library. | ||
| @dataclass(frozen=True) | ||
| class Laptop: | ||
| id: int | ||
| manufacturer: str | ||
| model: str | ||
| screen_size_in_inches: float | ||
| operating_system: OperatingSystem | ||
|
|
||
| # -------------------------------------------------------------------- | ||
| # BUSINESS LOGIC | ||
| # -------------------------------------------------------------------- | ||
| """ | ||
| Filters laptops to find those matching the user's preferred OS. | ||
|
|
||
| Args: | ||
| laptops: List of available laptops | ||
| person: User with OS preference | ||
|
|
||
| Returns: | ||
| List of laptops matching the user's preferred OS | ||
| """ | ||
| def find_possible_laptops(laptops: List[Laptop], person: Person) -> List[Laptop]: | ||
| possible_laptops = [] | ||
| for laptop in laptops: | ||
| if laptop.operating_system == person.preferred_operating_systems: | ||
| possible_laptops.append(laptop) | ||
| return possible_laptops | ||
|
|
||
|
|
||
| # -------------------------------------------------------------------- | ||
| # MAIN PROGRAM - USER INPUT AND PROCESSING | ||
| # -------------------------------------------------------------------- | ||
| # Get user input as strings first (raw input) | ||
| name = input("Enter your name: ") | ||
| age_str = input("Enter your age: ") | ||
| os_str = input("Enter preferred OS: ") | ||
|
|
||
| # -------------------------------------------------------------------- | ||
| # INPUT VALIDATION AND CONVERSION | ||
| # -------------------------------------------------------------------- | ||
|
|
||
| # Convert age from string to integer with error handling | ||
| # Output to stderr as per requirements, exit with non-zero code | ||
| try: | ||
| age = int(age_str) | ||
| except ValueError: | ||
|
|
||
| print(f"Error: {age_str} is not a valid age", file=sys.stderr) | ||
| sys.exit(1) | ||
|
|
||
| # Convert OS string to enum with error handling | ||
| try: | ||
| os = OperatingSystem(os_str) | ||
| except ValueError: | ||
| print(f"Error: '{os_str}' is not a valid OS. Choose: Ubuntu, macOS, Arch Linux", file=sys.stderr) | ||
| sys.exit(1) | ||
|
|
||
|
|
||
| # Create Person object from validated input | ||
| # Now we know age is a valid int and os is a valid OperatingSystem | ||
| person = Person(name=name, age=age, preferred_operating_systems=os) | ||
|
|
||
| # -------------------------------------------------------------------- | ||
| # DATA - AVAILABLE LAPTOPS | ||
| # -------------------------------------------------------------------- | ||
| # Pre-defined list of laptops in the library (as per exercise requirements) | ||
| laptops = [ | ||
| Laptop(id=1, manufacturer="Dell", model="XPS", screen_size_in_inches=13, operating_system=OperatingSystem.ARCH), | ||
| Laptop(id=2, manufacturer="Dell", model="XPS", screen_size_in_inches=15, operating_system=OperatingSystem.UBUNTU), | ||
| Laptop(id=3, manufacturer="Dell", model="XPS", screen_size_in_inches=15, operating_system=OperatingSystem.UBUNTU), | ||
| Laptop(id=4, manufacturer="Apple", model="macBook", screen_size_in_inches=13, operating_system=OperatingSystem.MACOS), | ||
| ] | ||
|
|
||
|
|
||
| # -------------------------------------------------------------------- | ||
| # PROCESSING - FIND MATCHING LAPTOPS | ||
| # -------------------------------------------------------------------- | ||
| # Find laptops matching user's preferred OS | ||
| possible_laptops = find_possible_laptops(laptops, person) | ||
|
|
||
| # Requirement: Tell user how many laptops have their OS | ||
| print(f"we have {len(possible_laptops)} laptops with {os.value}") | ||
|
|
||
|
|
||
| # -------------------------------------------------------------------- | ||
| # COUNTING LAPTOPS PER OPERATING SYSTEM | ||
| # -------------------------------------------------------------------- | ||
| # Count laptops for each OS to find which has most | ||
| arch_count = 0 | ||
| ubuntu_count = 0 | ||
| macos_count = 0 | ||
| for laptop in laptops: | ||
| if laptop.operating_system == OperatingSystem.ARCH: | ||
| arch_count += 1 | ||
| elif laptop.operating_system == OperatingSystem.UBUNTU: | ||
| ubuntu_count += 1 | ||
| else: | ||
| macos_count += 1 | ||
|
|
||
|
|
||
|
|
||
|
|
||
| # -------------------------------------------------------------------- | ||
| # FINDING THE OPERATING SYSTEM WITH MOST LAPTOPS | ||
| # -------------------------------------------------------------------- | ||
| # Store counts in dictionary for easy max calculation | ||
| os_counts = { | ||
| OperatingSystem.ARCH: arch_count, | ||
| OperatingSystem.UBUNTU: ubuntu_count, | ||
| OperatingSystem.MACOS: macos_count | ||
| } | ||
|
|
||
| # Find OS with maximum laptops | ||
| max_os = max(os_counts, key=os_counts.get) # Gets the OS with highest count | ||
| max_count = os_counts[max_os] # The actual count | ||
|
|
||
|
|
||
| # -------------------------------------------------------------------- | ||
| # COMPARISON AND SUGGESTION LOGIC | ||
| # -------------------------------------------------------------------- | ||
| # Get user's OS choice and count | ||
| os_user= person.preferred_operating_systems | ||
| user_count = os_counts[os_user] | ||
|
|
||
|
|
||
|
|
||
| # Requirement: Suggest alternative if another OS has MORE laptops | ||
| # Check: 1) Different OS, AND 2) Has strictly more laptops (> not >=) | ||
| if os_user != max_os and max_count > user_count: | ||
| print(f" if you're willing to accept {max_os.value} " + | ||
| f"you'd have {max_count} laptops available " + | ||
| f"(vs {user_count} for {os_user.value})") | ||
|
|
||
|
|
||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| #✍️exercise | ||
| #Fix the above code so that it works. You must not change the print on line 17 - we do want to print the children’s ages. (Feel free to invent the ages of Imran’s children.) | ||
|
|
||
|
|
||
|
|
||
| from dataclasses import dataclass | ||
|
|
||
| @dataclass(frozen=True) | ||
| class Person: | ||
| name: str | ||
| children: list["Person"] | ||
| age: int | ||
|
|
||
| fatma = Person(name="Fatma", age=7, children=[]) | ||
| aisha = Person(name="Aisha", age=10, children=[]) | ||
|
|
||
| imran = Person(name="Imran",age=50, children=[fatma, aisha]) | ||
|
|
||
| def print_family_tree(person: Person) -> None: | ||
| print(person.name) | ||
| for child in person.children: | ||
| print(f"- {child.name} ({child.age})") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this print grandchildren recursively? |
||
|
|
||
| print_family_tree(imran) | ||
|
|
||
|
|
||
| # When I first ran mypy with `children: list`, it found no errors. | ||
| # This is because mypy didn't know what type of items were in the list. | ||
| # After changing to `children: List["Person"]` (using generics), | ||
| # mypy could identify that each child is a Person object. | ||
| # Now it caught the bug: Person has no "age" attribute. | ||
| # I fixed this by adding `age: int` to the Person class. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| """ ✍️exercise | ||
| Play computer with this code. Predict what you expect each line will do. Then run the code and check your predictions. (If any lines cause errors, you may need to comment them out to check later lines). | ||
| """ | ||
|
|
||
| # PREDICTION: This defines a base class called Parent | ||
| class Parent: | ||
| # PREDICTION: Constructor that sets first_name and last_name attributes | ||
| def __init__(self, first_name: str, last_name: str): | ||
| self.first_name = first_name | ||
| self.last_name = last_name | ||
|
|
||
| # PREDICTION: Method that returns full name as "first last" | ||
| def get_name(self) -> str: | ||
| return f"{self.first_name} {self.last_name}" | ||
|
|
||
|
|
||
| # PREDICTION: Child class inherits everything from Parent class | ||
| class Child(Parent): | ||
| # PREDICTION: Constructor calls parent's constructor then adds new attribute | ||
| def __init__(self, first_name: str, last_name: str): | ||
| super().__init__(first_name, last_name) # PREDICTION: Calls Parent.__init__ | ||
| self.previous_last_names = [] # PREDICTION: Creates empty list for this instance | ||
|
|
||
| # PREDICTION: Method to change last name and track previous names | ||
| def change_last_name(self, last_name) -> None: | ||
| self.previous_last_names.append(self.last_name) | ||
| self.last_name = last_name | ||
|
|
||
| # PREDICTION: Method that returns full name with maiden name note if changed | ||
| def get_full_name(self) -> str: | ||
| suffix = "" | ||
| if len(self.previous_last_names) > 0: | ||
| suffix = f" (née {self.previous_last_names[0]})" | ||
| return f"{self.first_name} {self.last_name}{suffix}" | ||
|
|
||
|
|
||
| # PREDICTION: Creates Child instance with names "Elizaveta" "Alekseeva" | ||
| person1 = Child("Elizaveta", "Alekseeva") | ||
| # PREDICTION: Prints "Elizaveta Alekseeva" (calls inherited get_name() from Parent) | ||
| print(person1.get_name()) | ||
| # PREDICTION: Prints "Elizaveta Alekseeva" (no suffix since no name change yet) | ||
| print(person1.get_full_name()) | ||
| # PREDICTION: Changes last name to "Tyurina", adds "Alekseeva" to previous_last_names | ||
| person1.change_last_name("Tyurina") | ||
| # PREDICTION: Prints "Elizaveta Tyurina" (updated last name) | ||
| print(person1.get_name()) | ||
| # PREDICTION: Prints "Elizaveta Tyurina (née Alekseeva)" (shows maiden name) | ||
| print(person1.get_full_name()) | ||
|
|
||
|
|
||
| # PREDICTION: Creates Parent instance (NOT Child) with same names | ||
| person2 = Parent("Elizaveta", "Alekseeva") | ||
| # PREDICTION: Prints "Elizaveta Alekseeva" (Parent has get_name() method) | ||
| print(person2.get_name()) | ||
| # PREDICTION: ERROR! Parent class doesn't have get_full_name() method | ||
| print(person2.get_full_name()) | ||
| # PREDICTION: ERROR! Parent class doesn't have change_last_name() method | ||
| person2.change_last_name("Tyurina") | ||
| # PREDICTION: Won't reach this line due to previous error | ||
| print(person2.get_name()) | ||
| # PREDICTION: Won't reach this line due to previous error | ||
| print(person2.get_full_name()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| from datetime import date | ||
|
|
||
| class Person: | ||
| def __init__(self, name: str, date_of_birth: date, preferred_operating_system: str): | ||
| self.name = name | ||
| self.date_of_birth = date_of_birth | ||
| self.preferred_operating_system = preferred_operating_system | ||
|
|
||
| def is_adult(self) -> bool: | ||
| today_date = date.today().year | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Be careful about the formatting and indentation here |
||
| birth_year = self.date_of_birth.year | ||
| age = today_date - birth_year | ||
|
|
||
|
|
||
| return age >= 18 | ||
|
|
||
| imran = Person("Imran", date(2000, 6, 20), "Ubuntu") | ||
| print(imran.name) | ||
|
|
||
|
|
||
| eliza = Person("Eliza", date(1987, 12, 10), "Arch Linux") | ||
| print(eliza.name) | ||
|
|
||
|
|
||
|
|
||
| # when I ran mypy I got this errors: | ||
| # person.py:9: error: "Person" has no attribute "address" [attr-defined] | ||
| # person.py:13: error: "Person" has no attribute "address" [attr-defined] | ||
| # So I will remove .address line as mypy caught rightly that Person class does not have address property. It shows the benefit of using classes as earlier without defining a class mypy could not catch the same bug. | ||
|
|
||
|
|
||
| #def get_address(person: Person) -> str: | ||
| #return person.address | ||
|
|
||
| #print(is_adult(imran)) | ||
| # When I ran mypy with is_adult function I got no error as age is a property of Person class | ||
|
|
||
| # however when I added get_address function and ran mypy again I got this error: person.py:27: error: "Person" has no attribute "address" [attr-defined]Found 1 error in 1 file (checked 1 source file) which was expected as Person class has no address attribute | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To improve the user experience, you might want to do some validation on the input and ask for reentry if it is invalid. You might also want to give some options when asking for an OS