-
-
Notifications
You must be signed in to change notification settings - Fork 42
Manchester | 25-SDC-Nov | Geraldine Edwards | Sprint 5 | Prep Exercises #280
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
base: main
Are you sure you want to change the base?
Changes from all commits
d21625b
ff67f81
5e0ebd9
093032b
fd85eca
1f27187
7d8d652
ec5c6ce
cd028dc
56e7745
6dc57fd
e367c1d
491e971
8aed46e
3840632
630ba6f
1e06469
8063ab7
5cc2330
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,4 @@ | ||
| node_modules | ||
| .venv | ||
| .mypy_cache | ||
| requirements.txt |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # ------------------------ | ||
| # Exercise 1 | ||
| # ------------------------ | ||
| # Q. What does double("22") return? Did it do what you expected? Why did it return the value it did? | ||
| # A. I expected the result to be 44 through coercion, like JavaScript. | ||
| # It returned the string repeated i.e. 2222 because Python is a strongly typed language and doesn't use coercion | ||
|
|
||
| def double(input): | ||
| return input * 2 | ||
|
|
||
| print(double("22")) # returns "2222" | ||
|
|
||
|
|
||
|
|
||
| # ------------------------ | ||
| # Exercise 2 | ||
| # ------------------------ | ||
| # Q. Read the code and write down what the bug is. How would you fix it? | ||
| # A. In this function it returns the value of 30, which is the input * 3 which is logically correct, however you would | ||
| # expect the function to be called "triple" or you would expect the logic to be number * 2, so i would amend | ||
| # the function to either of these solutions | ||
|
|
||
| # original function (buggy): | ||
| def double(number): | ||
| return number * 3 | ||
|
|
||
| print(double(10)) # returns "30" | ||
|
|
||
|
|
||
| # solution 1 - fix the logic: | ||
| def double(number): | ||
| return number * 2 | ||
|
|
||
| print(double(10)) # returns "20" | ||
|
|
||
|
|
||
|
|
||
| # solution 2 - rename the function: | ||
| def triple(number): | ||
| return number * 3 | ||
|
|
||
| print(triple(10)) # returns "30" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| # def double(n: int) -> int: | ||
| # return n * 2 | ||
|
|
||
| # num = double(21) | ||
| # print(num) | ||
|
|
||
|
|
||
|
|
||
| from typing import List, Dict, Counter, Set, Union, Any | ||
| from math import sqrt | ||
|
|
||
|
|
||
| def average(nums: List[int]) -> float: | ||
| total = sum(nums) | ||
| count = len(nums) | ||
| return total / count | ||
|
|
||
| print(average([1, 2, 3, 4])) # 2.5 | ||
|
|
||
|
|
||
| # from typing import Dict | ||
| def get_total_marks(scorecard: Dict[str, int]) -> int: | ||
| marks = list(scorecard.values()) # marks : List[int] | ||
| return sum(marks) | ||
|
|
||
| scores = {'english': 84, 'maths': 92, 'history': 75} | ||
| print(get_total_marks(scores)) # 251 | ||
|
|
||
|
|
||
| # from typing import Counter, List | ||
| def count_occurences(data: List[float]) -> Counter[float]: | ||
| occurences = Counter(data) | ||
| return occurences | ||
|
|
||
| nums = [2.5, 1.0, 7, 1, 6, 2.5, 1.0] | ||
| print(count_occurences(nums)) # Counter({1.0: 3, 2.5: 2, 7: 1, 6: 1}) | ||
|
|
||
|
|
||
| # from typing import List | ||
| def unique_count(nums: List[int]) -> int: | ||
| """counts the number of unique items in the list""" | ||
| uniques = set() # How does mypy know what type this is? | ||
| for num in nums: | ||
| uniques.add(num) | ||
|
|
||
| return len(uniques) | ||
|
|
||
| print(unique_count([1, 2, 1, 3, 1, 2, 4, 3, 1])) # 4 | ||
|
|
||
|
|
||
| # from typing import List, Set | ||
| def unique_count(nums: List[int]) -> int: | ||
| """counts the number of unique items in the list""" | ||
| uniques: Set[int] = set() # Manually added type information | ||
| for num in nums: | ||
| uniques.add(num) | ||
|
|
||
| return len(uniques) | ||
|
|
||
| print(unique_count([1, 2, 1, 3, 1, 2, 4, 3, 1])) # 4 | ||
|
|
||
|
|
||
| # from typing import List, Set | ||
| def unique_count(nums: List[int]) -> int: | ||
|
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. Not sure why you've got 3 versions of this function here?
Author
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. @OracPrime Haha, yes it is a bit confusing at first glance but this was some exercises from an article (https://dev.to/tusharsadhwani/the-comprehensive-guide-to-mypy-561m) that we had to complete. |
||
| """counts the number of unique items in the list""" | ||
| uniques: Set[int] = set() | ||
| for num in nums: | ||
| uniques.add(num) | ||
|
|
||
| return len(uniques) | ||
|
|
||
| counts = unique_count([1, 2, 1, 3, 1, 2, 4, 3, 1]) | ||
|
|
||
| reveal_type(counts) # The special magic reveal_type method - | ||
| # you should only use reveal_type to debug your code, and remove it when you're done debugging. | ||
|
|
||
|
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. And if it's "remove it when you're done debugging" it should probably never be checked in to github except commented out
Author
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. As before, everything in this file is from an article and we just had to work through the exercises to deepen understanding - its not any code/program that I have totally written myself. :D |
||
|
|
||
| def print_favorite_color(person): | ||
| fav_color = person.get('favorite-color') | ||
| if fav_color is None: | ||
| print("You don't have a favorite color. 😿") | ||
| else: | ||
| print(f"Your favorite color is {fav_color}! 😸.") | ||
|
|
||
| me = {'name': 'Tushar', 'favorite-color': 'Purple'} | ||
| print_favorite_color(me) | ||
|
|
||
|
|
||
| def print_favorite_color(person: Dict[str, str]) -> None: # added types to function definition | ||
|
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. Again it's odd to have before and after methods both here and live. The second one will "win" so later code will call it, but maybe better to remove or comment out the before version |
||
| fav_color = person.get('favorite-color') | ||
| reveal_type(fav_color) # added this line here | ||
|
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. Again, don't leave reveal_type in for anything checked in. |
||
| if fav_color is None: | ||
| print("You don't have a favorite color. 😿") | ||
| else: | ||
| print(f"Your favorite color is {fav_color}! 😸.") | ||
|
|
||
| me = {'name': 'Tushar', 'favorite-color': 'Purple'} | ||
| print_favorite_color(me) | ||
|
|
||
|
|
||
| def print_item(item): | ||
| if isinstance(item, list): | ||
| for data in item: | ||
| print(data) | ||
| else: | ||
| print(item) | ||
|
|
||
| print_item('Hi!') | ||
| print_item(['This is a test', 'of polymorphism']) | ||
|
|
||
|
|
||
| def print_item(item: Union[str, List[str]]) -> None: | ||
|
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. Before version and reveal_types still here, too |
||
| reveal_type(item) | ||
|
|
||
| if isinstance(item, list): | ||
| for data in item: | ||
| reveal_type(item) | ||
| print(data) | ||
| else: | ||
| reveal_type(item) | ||
| print(item) | ||
|
|
||
| print_item('Hi!') | ||
| print_item(['This is a test', 'of polymorphism']) | ||
|
|
||
|
|
||
| #a type-annotated Python implementation of the builtin function abs: | ||
| def my_abs(num: Union[int, float, complex]) -> float: | ||
| if isinstance(num, complex): | ||
| # absolute value of a complex number is sqrt(i^2 + j^2) | ||
| return sqrt(num.real ** 2 + num.imag ** 2) | ||
|
|
||
| else: | ||
| return num if num > 0 else -num | ||
|
|
||
| print(my_abs(-5.6)) # 5.6 | ||
| print(my_abs(42)) # 42 | ||
| print(my_abs(0)) # 0 | ||
| print(my_abs(6-8j)) # 10.0 | ||
|
|
||
|
|
||
| # if you ever try to run reveal_type inside an untyped function, this is what happens: | ||
| def average(nums): | ||
| total = sum(nums) | ||
| count = len(nums) | ||
|
|
||
| ans = total / count | ||
| reveal_type(ans) # revealed type says it is 'Any' - 'Any' turns off type checking - so, avoid it if poss! | ||
|
|
||
|
|
||
| def post_data_to_api(data: Any) -> None: | ||
| requests.post('https://example.com/post', json=data) | ||
|
|
||
| data = '{"num": 42, "info": null}' | ||
| parsed_data = json.loads(data) | ||
| reveal_type(parsed_data) # Revealed type is 'Any' | ||
|
|
||
| post_data_to_api(data) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # ------------------------ | ||
| # Exercise 1 | ||
| # ------------------------ | ||
| # Read the code to understand what it’s trying to do. | ||
| # Add type annotations to the method parameters and return types of this code. | ||
| # Run the code through mypy, and fix all of the bugs that show up. | ||
| # When you’re confident all of the type annotations are correct, and the bugs are fixed, run the code and check it works. | ||
|
|
||
|
|
||
|
|
||
| # added type annotations to balances of dictionary[string, integer] to represent the structure needed, string to name and integer to amount, | ||
| # with the return type of none (as there is nothing to return) | ||
| def open_account(balances: dict[str, int], name: str, amount: int) -> None: | ||
| balances[name] = amount | ||
|
|
||
| # added type annotation of dictionary to the accounts argument because it reflects the balances argument, | ||
| # and a return type of integer. | ||
| def sum_balances(accounts: dict[str, int]) -> int: | ||
| total = 0 | ||
| for name, pence in accounts.items(): | ||
| print(f"{name} had balance {pence}") | ||
|
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. This is a criticism of the problem, not your solution. It's generally a really bad idea to put print statements inside functions people call to do stuff. You've no idea where this is being called from so spewing stuff to stdout is... reckless. Not your issue, problem with the problem, but worth remembering.
Author
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. Thanks David, appreciate your advice there. :) |
||
| total += pence | ||
| return total | ||
|
|
||
| # added integer type annotation and a return type of string for the output and renamed the function | ||
| # parameter to avoid shadowing issue with variable name in the outer scope | ||
| def format_pence_as_string(total_pence_amount: int) -> str: | ||
| if total_pence_amount < 100: | ||
|
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. What happens if the account has a negative balance?
Author
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. @OracPrime Good question! :) 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. I may have missed something, but I surely if total_pence_amount is -150 then you want £-1.50 whereas it will return -150p ??? I haven't run it, I'm just reading it. |
||
| return f"{total_pence_amount}p" | ||
| pounds = int(total_pence_amount / 100) | ||
| pence = total_pence_amount % 100 | ||
| return f"£{pounds}.{pence:02d}" | ||
|
|
||
| # renamed variable from 'balances' to 'account_balances' to avoid shadowing the function parameter | ||
| # 'balances' in open_account function. | ||
| account_balances = { | ||
| "Sima": 700, | ||
| "Linn": 545, | ||
| "Georg": 831, | ||
| } | ||
|
|
||
| # added the balances dictionary as first argument (as required above) | ||
| # and altered the amounts types from float 9.13 and string "7.13" to integers | ||
| open_account(account_balances, "Tobi", 913) | ||
| open_account(account_balances, "Olya", 713) | ||
|
|
||
| total_pence = sum_balances(account_balances) | ||
| # corrected the function name (originally was "format_pence_as_str") | ||
| total_string = format_pence_as_string(total_pence) | ||
|
|
||
| print(f"The bank accounts total {total_string}") | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| class Person: | ||
| def __init__(self, name: str, age: int, preferred_operating_system: str): | ||
| self.name = name | ||
| self.age = age | ||
| self.preferred_operating_system = preferred_operating_system | ||
|
|
||
|
|
||
|
|
||
| imran = Person("Imran", 22, "Ubuntu") | ||
| print(imran.name) | ||
| # print(imran.address) # mypy: Person has no attribute address | ||
|
|
||
|
|
||
| eliza = Person("Eliza", 34, "Arch Linux") | ||
| print(eliza.name) | ||
| # print(eliza.address) # mypy: Person has no attribute address | ||
|
|
||
|
|
||
| def is_adult(person: Person) -> bool: | ||
| return person.age >= 18 | ||
|
|
||
|
|
||
| print(is_adult(imran)) | ||
|
|
||
|
|
||
| # ------------------------ | ||
| # Exercise 1 | ||
| # ------------------------ | ||
| # Write a new function in the file that accepts a Person as a parameter and tries to access | ||
| # a property that doesn’t exist. Run it through mypy and check that it does report an error. | ||
|
|
||
| def is_located_in(person: Person) -> bool: | ||
| return person.is_located_in == "Manchester" | ||
|
|
||
| print(is_located_in(eliza)) # prints: AttributeError: 'Person' object has no attribute 'is_located_in' | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| from datetime import date | ||
|
|
||
|
|
||
| # ------------------------ | ||
| # Exercise 1 | ||
| # ------------------------ | ||
| # Q. Advantages of methods over free functions | ||
| # A. Encapsulation - Grouping the data (the attributes) and the behaviour (the methods) together | ||
| # means that the code is more organised and easier to understand. It also allows you to hide | ||
| # the internal details of how the class works to other parts of the program, so changes | ||
| # to the class can be made without affecting external code. Plus, it prevents | ||
| # accidental changes to the class's data from outside the class. | ||
|
|
||
| # Ease of documentation - When you use methods, all the actions (functions) related to a specific | ||
| # thing (like a Person or a String) are grouped inside the class for that thing. This makes it | ||
| # easier to find and understand what that thing can do because everything is in one place. | ||
|
|
||
| # Inheritance and reusability - methods can be inherited and overridden in subclasses, making them | ||
| # reusable and offering customization. | ||
|
|
||
| # Access to state - Methods can directly access and modify the data (attributes) of an object using | ||
| # self, which free functions cannot do as they are not tied to any specific object. | ||
|
|
||
| # Polymorphism - Methods support this by allowing you to define methods with the same name in | ||
| # different classes, you call the same methods on different objects but each with behaviour, | ||
| # that is specific to that class. | ||
|
|
||
|
|
||
| # ------------------------ | ||
| # Exercise 2 | ||
| # ------------------------ | ||
| # Change the Person class to take a date of birth (using the standard library’s datetime.date class) | ||
| # and store it in a field instead of age. Update the is_adult method to act the same as before. | ||
|
|
||
| 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.today() | ||
| age = today.year - self.date_of_birth.year - ((today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day)) | ||
| return age >= 18 | ||
|
|
||
| imran = Person("Imran", date(2002, 1, 12), "Ubuntu") | ||
| print(imran.is_adult()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| # class Person: | ||
| # def __init__(self, name: str, age: int, preferred_operating_system: str): | ||
| # self.name = name | ||
| # self.age = age | ||
| # self.preferred_operating_system = preferred_operating_system | ||
|
|
||
| # imran = Person("Imran", 22, "Ubuntu") | ||
| # imran2 = Person("Imran", 22, "Ubuntu") | ||
|
|
||
| # print(imran == imran2) # Prints False | ||
| # print(imran) # <__main__.Person object at 0x74b1986e2990> | ||
|
|
||
| from dataclasses import dataclass | ||
| from datetime import date | ||
|
|
||
| # @dataclass(frozen=True) | ||
| # class Person: | ||
| # name: str | ||
| # age: int | ||
| # preferred_operating_system: str | ||
|
|
||
| # imran = Person("Imran", 22, "Ubuntu") # We can call this constructor - @dataclass generated it for us. | ||
| # print(imran) # Prints Person(name='Imran', age=22, preferred_operating_system='Ubuntu') | ||
|
|
||
| # imran2 = Person("Imran", 22, "Ubuntu") | ||
| # print(imran == imran2) # Prints True | ||
|
|
||
|
|
||
| # ------------------------ | ||
| # Exercise 1 | ||
| # ------------------------ | ||
| # Q.Write a Person class using @datatype which uses a datetime.date for date of birth, rather than an int for age. | ||
| # Re-add the is_adult method to it. | ||
|
|
||
| @dataclass | ||
| class Person: | ||
| name: str | ||
| date_of_birth: date | ||
| preferred_operating_system: str | ||
|
|
||
| def is_adult(self) -> bool: | ||
| today = date.today() | ||
| age = today.year - self.date_of_birth.year - ((today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day)) | ||
| return age >= 18 | ||
|
|
||
| imran = Person("Imran", date(2002, 1, 12), "Ubuntu") | ||
| print(imran.is_adult()) |
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.
No, Python isn't a strongly typed language, it's "duck typed". However it is as you point out more sensible in how it behaves than javascript.
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.
@OracPrime Thanks for the extra context here David :)
Every resource I went to said it was strongly typed! :D
I have done a bit more reading on the differences between dynamic, static, strong, and duck typing. For me, Python is strongly typed because it doesn’t automatically mix incompatible types like "22" + 2. Also it is dynamically typed, not statically typed, so variable types are checked at runtime and can change. I agree with you about it being duck typed though because even though it enforces type rules at runtime, it still lets objects work if the operation makes sense. :) (No wonder Python is popular!)
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.
Static typing, duck typing, dynamic typing, .... and the madness which is javascript!