From dcc6ad5861f55199858173f83237fdeb2af46c2e Mon Sep 17 00:00:00 2001 From: l Date: Wed, 9 Sep 2026 15:56:45 +0100 Subject: [PATCH 01/13] add first exercises --- sprint-5/01-predict.py | 40 +++++++++++++++++++++++++++++++++++++ sprint-5/02-playcomputer.js | 27 +++++++++++++++++++++++++ sprint-5/03-fix.py | 9 +++++++++ sprint-5/04-addmypy.py | 39 ++++++++++++++++++++++++++++++++++++ sprint-5/05-explain.py | 20 +++++++++++++++++++ sprint-5/README.md | 11 ++++++++++ sprint-5/requirements.txt | 1 + 7 files changed, 147 insertions(+) create mode 100644 sprint-5/01-predict.py create mode 100644 sprint-5/02-playcomputer.js create mode 100644 sprint-5/03-fix.py create mode 100644 sprint-5/04-addmypy.py create mode 100644 sprint-5/05-explain.py create mode 100644 sprint-5/README.md create mode 100644 sprint-5/requirements.txt diff --git a/sprint-5/01-predict.py b/sprint-5/01-predict.py new file mode 100644 index 000000000..fca6e4b1a --- /dev/null +++ b/sprint-5/01-predict.py @@ -0,0 +1,40 @@ +def half(value): + return value / 2 + +def double(value): + return value * 2 + +def second(value): + return value[1] + +# 1. Predict what you think will happen with each of the following functions +# 2. Then, test it, and explain in your own words what is actually happening and why +# (feel free to comment out lines if you think they cause errors or crashes while testing) + +print(half(22)) +# Prediction: +# What actually happens and why: + +print(half("22")) +# Prediction: +# What actually happens and why: + +print(double(22)) +# Prediction: +# What actually happens and why: + +print(double("22")) +# Prediction: +# What actually happens and why: + +print(second(22)) +# Prediction: +# What actually happens and why: + +print(second(0x16)) +# Prediction: +# What actually happens and why: + +print(second("22")) +# Prediction: +# What actually happens and why: diff --git a/sprint-5/02-playcomputer.js b/sprint-5/02-playcomputer.js new file mode 100644 index 000000000..2fad07c1c --- /dev/null +++ b/sprint-5/02-playcomputer.js @@ -0,0 +1,27 @@ +import process from "node:process"; +import readline from "node:readline"; + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +rl.question("What URL should we fetch?\n> ", async (url) => { + const response = await fetch(url); + if (!response.ok) { + if (response.body.toLowerCase().includes("permission")) { + console.error("You didn't have permission to get that URL"); + } else { + console.error(`The request failed - body: ${response.body}`); + } + process.exit(1); + } + + const contents = await response.json(); + + console.log(contents); + + rl.close(); +}); + +// Task: Leave a comment on any line that you can see has some errors explaining what you think the problem is diff --git a/sprint-5/03-fix.py b/sprint-5/03-fix.py new file mode 100644 index 000000000..477b11f43 --- /dev/null +++ b/sprint-5/03-fix.py @@ -0,0 +1,9 @@ +def double(number): + return number * 3 + +print(double(10)) + +# Task: +# What is the bug here? +# How could you fix it? +# Are there multiple possible ways to fix it? diff --git a/sprint-5/04-addmypy.py b/sprint-5/04-addmypy.py new file mode 100644 index 000000000..e1abe7c45 --- /dev/null +++ b/sprint-5/04-addmypy.py @@ -0,0 +1,39 @@ +def open_account(balances, name, amount): + balances[name] = amount + +def sum_balances(accounts): + 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): + 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("Tobi", 9.13) +open_account("Olya", "£7.13") + +total_pence = sum_balances(balances) +total_string = format_pence_as_str(total_pence) + +print(f"The bank accounts total {total_string}") + +# TASK +# This code contains bugs related to types. They are bugs mypy can catch. +# +# 1. Read this code to understand what it's trying to do. +# 2. Install and set up mypy in a python virtual environment +# 3. Add type annotations everywhere appropriate +# 4. Run `mypy 04-addmypy.py`, and fix any errors +# 5. When you're confident all of the type annotations are correct, and the bugs are fixed, run the code and check it works. diff --git a/sprint-5/05-explain.py b/sprint-5/05-explain.py new file mode 100644 index 000000000..91cff19ad --- /dev/null +++ b/sprint-5/05-explain.py @@ -0,0 +1,20 @@ +imran = { + "name": "Imran", + "age": 22, + "preferred_operating_system": "Ubuntu", +} + +eliza = { + "name": "Eliza", + "age": 34, + "preferred_operating_system": "Arch Linux", +} + + +print(imran["name"]) +print(imran["address"]) + +# Task: +# Try running mypy on this file and see what happens +# Then try executing the file and see what happens +# What is happening here? diff --git a/sprint-5/README.md b/sprint-5/README.md new file mode 100644 index 000000000..ffd9d59d8 --- /dev/null +++ b/sprint-5/README.md @@ -0,0 +1,11 @@ +# Sprint 5 + +For this task, read through the prep material for sprint 5. + +Sprint 5 is all about types in prorgamming. + +This directory has some example code and exercises the prep will refer to. + +As you read through the prep, complete the tasks here. + +Then submit as a PR once you are finished. diff --git a/sprint-5/requirements.txt b/sprint-5/requirements.txt new file mode 100644 index 000000000..f0aa93ac8 --- /dev/null +++ b/sprint-5/requirements.txt @@ -0,0 +1 @@ +mypy From 31056a42d1b1514408041ca925cee4e7408b44b8 Mon Sep 17 00:00:00 2001 From: l Date: Wed, 16 Sep 2026 16:02:30 +0100 Subject: [PATCH 02/13] update classes --- sprint-5/{05-explain.py => 05-predict.py} | 4 ++-- sprint-5/06-classes.py | 28 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) rename sprint-5/{05-explain.py => 05-predict.py} (73%) create mode 100644 sprint-5/06-classes.py diff --git a/sprint-5/05-explain.py b/sprint-5/05-predict.py similarity index 73% rename from sprint-5/05-explain.py rename to sprint-5/05-predict.py index 91cff19ad..cc15650d2 100644 --- a/sprint-5/05-explain.py +++ b/sprint-5/05-predict.py @@ -16,5 +16,5 @@ # Task: # Try running mypy on this file and see what happens -# Then try executing the file and see what happens -# What is happening here? +# Predict what do you think will happen when you run this task? +# Can you explain what actualy happens? diff --git a/sprint-5/06-classes.py b/sprint-5/06-classes.py new file mode 100644 index 000000000..ff335765d --- /dev/null +++ b/sprint-5/06-classes.py @@ -0,0 +1,28 @@ +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) + +eliza = Person("Eliza", 34, "Arch Linux") +print(eliza.name) +print(eliza.address) + +# Task 6.1: +# Run mypy on this code +# Fix the code so there are no errors in mypy or when it runs + +# Task 6.2: +# Create a new function in this file called likes_apple +# It should take a person as parameter +# It returns true if the preferred operating system is "macOS" or "iOS" +# It should return false for any other preferred os +# Add all the appropriate type annotations and test it has no errors in mypy + +# Task 6.3: +# Compare objects and classes +# What are some advantages and disadvantages of each? From 5709d46c1bee2ea742830dd32fe5137b1a60616a Mon Sep 17 00:00:00 2001 From: l Date: Wed, 16 Sep 2026 16:53:46 +0100 Subject: [PATCH 03/13] update methods tasks --- sprint-5/07-methods.txt | 4 ++++ sprint-5/08-implement.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 sprint-5/07-methods.txt create mode 100644 sprint-5/08-implement.py diff --git a/sprint-5/07-methods.txt b/sprint-5/07-methods.txt new file mode 100644 index 000000000..28098862a --- /dev/null +++ b/sprint-5/07-methods.txt @@ -0,0 +1,4 @@ +Answer the following question: +What is the difference between a method and a function? +Can you give some advantages of methods over functions? + diff --git a/sprint-5/08-implement.py b/sprint-5/08-implement.py new file mode 100644 index 000000000..3b4f6540f --- /dev/null +++ b/sprint-5/08-implement.py @@ -0,0 +1,30 @@ +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.is_adult()) + +# Task: +# 1. Add the `drivers_license_check` free function and the `is_adult` method into the code +# Make sure your code currently gives the expected output. +# +# 2. Change the `Person` class to take a date of birth +# Use the standard library's `datetime.date` class +# https://docs.python.org/3/library/datetime.html#datetime.date +# Store the `date of birth` in a field instead of `age` (it should be a `str`) +# +# 3. Try to run your code +# How does this change break your code. +# What kind of error do you get? +# Is it helpful in identifying where your next change needs to be? +# Type your thoughts here: +# +# +# +# +# 4. Update the `is_adult` method so the error is fixed. +# Using the `drivers_license_check` function check everything runs as expected + From 4c4928b4b17a949d42cae1518313bef529902edc Mon Sep 17 00:00:00 2001 From: l Date: Wed, 16 Sep 2026 17:04:54 +0100 Subject: [PATCH 04/13] new dataclass task from poonam --- sprint-5/09-dataclass.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 sprint-5/09-dataclass.py diff --git a/sprint-5/09-dataclass.py b/sprint-5/09-dataclass.py new file mode 100644 index 000000000..857ef3379 --- /dev/null +++ b/sprint-5/09-dataclass.py @@ -0,0 +1,12 @@ +# Task 9.1: +# Copy the code you have so far from task 08-implement.py +# Convert this to a dataclass +# Test that the dataclass works with mypy, with equality checks + +# Task 9.2: +# Add a greeting method that says "Hello, !" + +# Task 9.3: +# Read the @datatype documentation here: https://docs.python.org/3/library/dataclasses.html +# Explain what does `frozen=True` do to the class? +# What other options could you play around with and explore? Offer suggestions for any that would be useful here. From 259c684c0a6a5706eb1c7bb5309e9a9bee291e2f Mon Sep 17 00:00:00 2001 From: l Date: Wed, 23 Sep 2026 15:25:12 +0100 Subject: [PATCH 05/13] generic tasks --- sprint-5/10-predict.py | 30 ++++++++++++++++++++ sprint-5/11-fix.py | 63 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 sprint-5/10-predict.py create mode 100644 sprint-5/11-fix.py diff --git a/sprint-5/10-predict.py b/sprint-5/10-predict.py new file mode 100644 index 000000000..f4a639b52 --- /dev/null +++ b/sprint-5/10-predict.py @@ -0,0 +1,30 @@ +from dataclasses import dataclass + +@dataclass(frozen=True) +class Animal: + name: str + species: str + +@dataclass(frozen=True) +class Person: + name: str + age: int + +@dataclass(frozen=True) +class FamilyTree: + parent: Person + members: list + +pet = Animal(name="Gromit", species="Dog") +fatma = Person(name="Fatma", age=4) +aisha = Person(name="Aisha", age=6) +imran = Person(name="Imran", age=30) + +family = FamilyTree(parent=imran, members=[fatma, aisha, pet]) + +def print_family_tree(family: FamilyTree): + print(family.parent.name) + for child in family.members: + print(f"{child.name} ({child.age} years old)") + +print_family_tree(family) diff --git a/sprint-5/11-fix.py b/sprint-5/11-fix.py new file mode 100644 index 000000000..bbeefe60a --- /dev/null +++ b/sprint-5/11-fix.py @@ -0,0 +1,63 @@ +from dataclasses import dataclass +from typing import List + +@dataclass(frozen=True) +class Animal: + name: str + size: str + +@dataclass(frozen=True) +class Person: + name: str + age: int + +@dataclass(frozen=True) +class Tree[T]: + parent: T + children: List[T] + + def print_tree(self): + print(self.parent) + for child in self.children: + print(child) + +fatma = Person(name="Fatma", age=4) +aisha = Person(name="Aisha", age=6) +imran = Person(name="Imran", age=30) +family_tree = Tree[Person](parent=imran, children=[fatma, aisha]) + +cats = Animal(name="Cat", size="Small") +dogs = Animal(name="Dog", size="Medium") +mammals = Animal(name="Mammals", size="Variable") +species_tree = Tree[Animal](parent=mammals, children=[cats, dogs]) + +family_tree.print_tree() +species_tree.print_tree() + +# Task 11: +# Experiment with mypy and make sure that the family tree only takes `Person` types and the species tree only takes `Animal` types. +# +# We are going to add printing to the above code. +# +# Unlike our earlier example, we want to avoid having to create two +# separate looping methods to print out an entire tree for each datatype. +# So we have created a single looping function within Tree that will work for any datatype. +# +# Currently the `Tree.print_tree()` function doesn't do anything. +# +# Add some appropriate methods to each of the Animal and Person classes to allow it to work. +# +# **Stretch task** +# +# Think of another type of data that can be organised into a tree. +# +# Add a new class for this, instantiate some variables, and have the existing `Tree` class print it out. +# Here is an example of what should be printed out +''' +Imran (30 years old) +- Fatma (4 years old) +- Aisha (6 years old)) +Mammals (Variable size) +- Cat (Small size) +- Dog (Medium size) +''' From 451130c7679c753cebadbc44639e072ce077785e Mon Sep 17 00:00:00 2001 From: l Date: Wed, 23 Sep 2026 15:42:33 +0100 Subject: [PATCH 06/13] refactoring --- sprint-5/12-refactor.py | 57 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 sprint-5/12-refactor.py diff --git a/sprint-5/12-refactor.py b/sprint-5/12-refactor.py new file mode 100644 index 000000000..9bf8be375 --- /dev/null +++ b/sprint-5/12-refactor.py @@ -0,0 +1,57 @@ +from dataclasses import dataclass +from typing import List + +@dataclass(frozen=True) +class Person: + name: str + age: int + preferred_operating_system: str + + +@dataclass(frozen=True) +class Laptop: + id: int + manufacturer: str + model: str + screen_size_in_inches: float + operating_system: str + + +def find_possible_laptops(laptops: List[Laptop], person: Person) -> List[Laptop]: + possible_laptops = [] + for laptop in laptops: + if laptop.operating_system == person.preferred_operating_system: + possible_laptops.append(laptop) + return possible_laptops + + +people = [ + Person(name="Imran", age=22, preferred_operating_system="Ubuntu"), + Person(name="Eliza", age=34, preferred_operating_system="Arch Linux"), +] + +laptops = [ + Laptop(id=1, manufacturer="Dell", model="XPS", screen_size_in_inches=13, operating_system="Arch Linux"), + Laptop(id=2, manufacturer="Dell", model="XPS", screen_size_in_inches=15, operating_system="Ubuntu"), + Laptop(id=3, manufacturer="Dell", model="XPS", screen_size_in_inches=15, operating_system="ubuntu"), + Laptop(id=4, manufacturer="Apple", model="macBook", screen_size_in_inches=13, operating_system="macOS"), +] + +for person in people: + possible_laptops = find_possible_laptops(laptops, person) + print(f"Possible laptops for {person.name}: {possible_laptops}") + +# Task 12 +#Try changing the type annotation of `Person.preferred_operating_system` from `str` to `List[str]`. +# +#Run mypy on the code. +# +#It tells us different places that our code is now wrong. Fix it to remov eany errors. +# +#Now we changed the types, we probably also want to _rename_ our fields to something appropriate. +# +#Run mypy again. +# +#Fix all of the places that mypy tells you need changing. +# +#Then, make sure the program works as you'd expect. From 880af4c3ffbf49b41759c8ca8db576858410bd31 Mon Sep 17 00:00:00 2001 From: l Date: Wed, 23 Sep 2026 16:09:30 +0100 Subject: [PATCH 07/13] enums --- sprint-5/13-implement.py | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 sprint-5/13-implement.py diff --git a/sprint-5/13-implement.py b/sprint-5/13-implement.py new file mode 100644 index 000000000..61bf1fbfc --- /dev/null +++ b/sprint-5/13-implement.py @@ -0,0 +1,55 @@ +from dataclasses import dataclass +from typing import List + +@dataclass(frozen=True) +class Person: + name: str + age: int + preferred_operating_system: str + + +@dataclass(frozen=True) +class Laptop: + id: int + manufacturer: str + model: str + screen_size_in_inches: float + operating_system: str + + +def find_possible_laptops(laptops: List[Laptop], person: Person) -> List[Laptop]: + possible_laptops = [] + for laptop in laptops: + if laptop.operating_system == person.preferred_operating_system: + possible_laptops.append(laptop) + return possible_laptops + + +people = [ + Person(name="Imran", age=22, preferred_operating_system="ubuntu"), + Person(name="Eliza", age=34, preferred_operating_system="arch"), +] + +laptops = [ + Laptop(id=1, manufacturer="Dell", model="XPS", screen_size_in_inches=13, operating_system="arch"), + Laptop(id=2, manufacturer="Dell", model="XPS", screen_size_in_inches=15, operating_system="ubuntu"), + Laptop(id=3, manufacturer="Dell", model="XPS", screen_size_in_inches=15, operating_system="ubuntu"), + Laptop(id=4, manufacturer="Apple", model="macBook", screen_size_in_inches=13, operating_system="macos"), +] + +for person in people: + possible_laptops = find_possible_laptops(laptops, person) + print(f"Possible laptops for {person.name}: {possible_laptops}") + +# Task 13 +# It currently handles operating systems as strings. +# +# Refactor the code to use enums for operating systems. +# +# Check with mypy and test it to ensure the program still works correctly. +# +# Replace the list of existing people with the `input` function to read a person's name, age, and preferred operating system. +# +# Make sure your implementation has a good user experience, and properly validates the inputs, mapping an OS to one of the enum values. +# +# If an operating system can't be matched at all, your script should handle it appropriately and not crash. From d88743d6e0746c491bce0b568a92918d0c736d7e Mon Sep 17 00:00:00 2001 From: l Date: Wed, 23 Sep 2026 16:38:32 +0100 Subject: [PATCH 08/13] inheritance --- sprint-5/14-analyse.py | 82 +++++++++++++++++++++++++++++++++++++ sprint-5/15-playcomputer.py | 42 +++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 sprint-5/14-analyse.py create mode 100644 sprint-5/15-playcomputer.py diff --git a/sprint-5/14-analyse.py b/sprint-5/14-analyse.py new file mode 100644 index 000000000..908db405e --- /dev/null +++ b/sprint-5/14-analyse.py @@ -0,0 +1,82 @@ +from typing import Iterable, Optional + +class ImmutableNumberList: + # We accept any `Iterable[int]` here, so can construct with a list, a set, or anything else that can be iterated. + def __init__(self, elements: Iterable[int]): + # We copy the elements so that if someone mutates the passed in elements list, our copy won't be mutated. + self.elements = [element for element in elements] + + def first(self) -> Optional[int]: + if not self.elements: + return None + return self.elements[0] + + def last(self) -> Optional[int]: + if not self.elements: + return None + return self.elements[-1] + + def length(self) -> int: + return len(self.elements) + + def largest(self) -> Optional[int]: + # To find the largest element, we need to go through the entire list (which may take some time). + if not self.elements: + return None + largest = self.elements[0] + for element in self.elements: + if element > largest: + largest = element + return largest + + +# A SortedImmutableNumberList is the same as an ImmutableNumberList, +# but it changes some aspects. +class SortedImmutableNumberList(ImmutableNumberList): + def __init__(self, elements: Iterable[int]): + # We do extra work here when constructing the list, + # to make sure the elements are sorted. + # This takes more time than the ImmutableNumberList version would. + super().__init__(sorted(elements)) + + # This method overrides (replaces) the method with the same name on the super-class. + def largest(self) -> Optional[int]: + # Because we know the elements were already sorted in the constructor, + # we can implement finding the largest number faster. + # We don't need to look through every element - we know the largest element is at the end. + # Because we did extra work one time before (in the constructor), + # we can avoid re-doing that work every time someone calls `largest()`. + return self.last() + + def max_gap_between_values(self) -> Optional[int]: + if not self.elements: + return None + previous_element = None + max_gap = -1 + for element in self.elements: + if previous_element is not None: + gap = element - previous_element + if gap > max_gap: + max_gap = gap + previous_element = element + return max_gap + + +values = SortedImmutableNumberList([1, 19, 7, 13, 4]) +print(values.largest()) +print(values.max_gap_between_values()) + +unsorted_values = ImmutableNumberList([1, 19, 7, 13, 4]) +print(unsorted_values.largest()) +print(unsorted_values.max_gap_between_values()) # This doesn't work - the superclass doesn't define this method. + + +# Task 14 +# Try using this code and make sure you understand how it works and what it does +# +# Answer the following questions, writing your answers in the file, before checking the answers. +# +# Q1: If you know in advance you need to frequently access the largest item of the list, which class will be more efficient and why? +# +# Q2: If you know in advance you will be initialising many of them repeatedly, which class will be more efficient and why? +# diff --git a/sprint-5/15-playcomputer.py b/sprint-5/15-playcomputer.py new file mode 100644 index 000000000..0616c8761 --- /dev/null +++ b/sprint-5/15-playcomputer.py @@ -0,0 +1,42 @@ +class Parent: + def __init__(self, first_name: str, last_name: str): + self.first_name = first_name + self.last_name = last_name + + def get_name(self) -> str: + return f"{self.first_name} {self.last_name}" + + +class Child(Parent): + def __init__(self, first_name: str, last_name: str): + super().__init__(first_name, last_name) + self.previous_last_names = [] + + def change_last_name(self, last_name) -> None: + self.previous_last_names.append(self.last_name) + self.last_name = last_name + + 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}" + +# Task: +# Play computer here +# Describe what is happening and why on each line below here +# If any lines cause errors, comment out the line and explain why the error happens + +person1 = Child("Elizaveta", "Alekseeva") +print(person1.get_name()) +print(person1.get_full_name()) +person1.change_last_name("Tyurina") +print(person1.get_name()) +print(person1.get_full_name()) + +person2 = Parent("Elizaveta", "Alekseeva") +print(person2.get_name()) +print(person2.get_full_name()) +person2.change_last_name("Tyurina") +print(person2.get_name()) +print(person2.get_full_name()) From c6a355cba93bf45784fc8b1abb3d530aecbde50b Mon Sep 17 00:00:00 2001 From: l Date: Wed, 30 Sep 2026 11:20:44 +0100 Subject: [PATCH 09/13] add encapsulation task --- sprint-5/09-encapsulation.txt | 8 ++++++++ sprint-5/{09-dataclass.py => 10-dataclass.py} | 0 sprint-5/{10-predict.py => 11-predict.py} | 0 sprint-5/{11-fix.py => 12-fix.py} | 0 sprint-5/{12-refactor.py => 13-refactor.py} | 0 sprint-5/{13-implement.py => 14-implement.py} | 0 sprint-5/{14-analyse.py => 15-analyse.py} | 0 sprint-5/{15-playcomputer.py => 16-playcomputer.py} | 0 8 files changed, 8 insertions(+) create mode 100644 sprint-5/09-encapsulation.txt rename sprint-5/{09-dataclass.py => 10-dataclass.py} (100%) rename sprint-5/{10-predict.py => 11-predict.py} (100%) rename sprint-5/{11-fix.py => 12-fix.py} (100%) rename sprint-5/{12-refactor.py => 13-refactor.py} (100%) rename sprint-5/{13-implement.py => 14-implement.py} (100%) rename sprint-5/{14-analyse.py => 15-analyse.py} (100%) rename sprint-5/{15-playcomputer.py => 16-playcomputer.py} (100%) diff --git a/sprint-5/09-encapsulation.txt b/sprint-5/09-encapsulation.txt new file mode 100644 index 000000000..a5550d701 --- /dev/null +++ b/sprint-5/09-encapsulation.txt @@ -0,0 +1,8 @@ +Read through Python encapsulation +https://www.w3schools.com/python/python_encapsulation.asp +and think about some of the benefits that encapsulation can add to a class. + +Do some further research of your own to learn about encapsulation. + +Think of some examples and in your own words write down some benefits and trade-offs of using encapsulation in classes: + diff --git a/sprint-5/09-dataclass.py b/sprint-5/10-dataclass.py similarity index 100% rename from sprint-5/09-dataclass.py rename to sprint-5/10-dataclass.py diff --git a/sprint-5/10-predict.py b/sprint-5/11-predict.py similarity index 100% rename from sprint-5/10-predict.py rename to sprint-5/11-predict.py diff --git a/sprint-5/11-fix.py b/sprint-5/12-fix.py similarity index 100% rename from sprint-5/11-fix.py rename to sprint-5/12-fix.py diff --git a/sprint-5/12-refactor.py b/sprint-5/13-refactor.py similarity index 100% rename from sprint-5/12-refactor.py rename to sprint-5/13-refactor.py diff --git a/sprint-5/13-implement.py b/sprint-5/14-implement.py similarity index 100% rename from sprint-5/13-implement.py rename to sprint-5/14-implement.py diff --git a/sprint-5/14-analyse.py b/sprint-5/15-analyse.py similarity index 100% rename from sprint-5/14-analyse.py rename to sprint-5/15-analyse.py diff --git a/sprint-5/15-playcomputer.py b/sprint-5/16-playcomputer.py similarity index 100% rename from sprint-5/15-playcomputer.py rename to sprint-5/16-playcomputer.py From a778b38554806f998fe9182a830a50693e8015bb Mon Sep 17 00:00:00 2001 From: l Date: Wed, 30 Sep 2026 11:20:56 +0100 Subject: [PATCH 10/13] add new stretch task --- sprint-5-stretch/README.md | 14 ++++++++++ sprint-5-stretch/requirements.txt | 1 + sprint-5-stretch/stretch-radio.py | 43 +++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 sprint-5-stretch/README.md create mode 100644 sprint-5-stretch/requirements.txt create mode 100644 sprint-5-stretch/stretch-radio.py diff --git a/sprint-5-stretch/README.md b/sprint-5-stretch/README.md new file mode 100644 index 000000000..a5888c97d --- /dev/null +++ b/sprint-5-stretch/README.md @@ -0,0 +1,14 @@ +# Sprint 5 Stretch - Radio Control App + +This is part of an app that can be used to control an online radio station + +Your task is to: +1. Upgrade everything use dataclasses +1. Observe how the given `__str__` method works, and add one to the other classes +1. When the `__str__` method is called, have it print the duration in formatted mm:ss rather than just seconds +1. Add a new class of `Person` - you can use your imagination what this includes, but it should have at least a string name +1. Turn the fields for _artists_, _host_, and _guest_ into `Person` classes +1. Make a new class `Podcast` that _extends_ the `TalkShow` class, with an extra string field for _sponsor_ +1. Finish implementing the class `Playlist`, and set it to takes a generic, e.g. either `Song` or `TalkShow` or `Podcast` + +As you implement, make sure to keep checking with mypy and add your own code at the end to test it works correctly diff --git a/sprint-5-stretch/requirements.txt b/sprint-5-stretch/requirements.txt new file mode 100644 index 000000000..f0aa93ac8 --- /dev/null +++ b/sprint-5-stretch/requirements.txt @@ -0,0 +1 @@ +mypy diff --git a/sprint-5-stretch/stretch-radio.py b/sprint-5-stretch/stretch-radio.py new file mode 100644 index 000000000..562a13500 --- /dev/null +++ b/sprint-5-stretch/stretch-radio.py @@ -0,0 +1,43 @@ +class Song: + def __init__(self, artist, duration, title): + self.artist = artist # should be a string + self.duration = duration # should be an integer in seconds + self.title = title # should be a string + + def __str__(self): + return f"{self.artist} - {self.title} ({self.duration})" + +class TalkShow: + def __init__(self, host, guests, duration, title): + self.host = host # should be a string + self.guests = guests # should be a list of strings + self.duration = duration # should be an integer in seconds + self.title = title # should be a string + +class Playlist: + def __init__(self, items): + self.items = items # A list of Song or TalkShow + self.currently_playing_index = 0 + + def play_next(self): + pass + # TODO: increment the currently_playing_index, when you get to the end loop back to 0 + + def get_currently_playing(self): + return "todo" + # TODO: return a string representation of whatever is playing at the current index + # See the __str__ method in Song for an example of how to make a string representation of a class + + def get_total_duration(self): + return 0 + # TODO: return + +song_a = Song("Rick Astley", 210, "Never Gonna Give You Up") +print(song_a) + +song_b = Song("Psy", 219, "Gangnam Style") +funny_songs = Playlist([song_a, song_b]) +print(funny_songs.get_currently_playing()) + +show_a = TalkShow("Melvyn Bragg", ["Jim Al-Khalili", "Sheila Rowan", "Carolin Crawford"], 2700, "Gravitational Waves") + From 087f3953e610b5089567befef38f0bdb9012f6d8 Mon Sep 17 00:00:00 2001 From: l Date: Wed, 30 Sep 2026 11:26:02 +0100 Subject: [PATCH 11/13] add encapsulation stretch task --- sprint-5/09-encapsulation.py | 34 ++++++++++++++++++++++++++++++++++ sprint-5/09-encapsulation.txt | 8 -------- 2 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 sprint-5/09-encapsulation.py delete mode 100644 sprint-5/09-encapsulation.txt diff --git a/sprint-5/09-encapsulation.py b/sprint-5/09-encapsulation.py new file mode 100644 index 000000000..a9d47561a --- /dev/null +++ b/sprint-5/09-encapsulation.py @@ -0,0 +1,34 @@ +class Person: + def __init__(self, name: str, age: int): + self.name = name + self.__age = age + + def is_adult(self): + return self.__age >= 18 + +imran = Person("Imran", 22) +print(imran.name) +print(imran.age) # fails +print(imran.__age) # fails +print(imran.is_adult()) # works and prints True + +eliza = Person("Eliza", 12) +print(eliza.name) +print(eliza.age) # fails +print(imran.__age) # fails +print(eliza.is_adult()) # works and prints False + +# Task 9 +# Read through Python encapsulation +# https://www.w3schools.com/python/python_encapsulation.asp +# and think about some of the benefits that encapsulation can add to a class. +# +# Do some further research of your own to learn about encapsulation. +# +# Think of some examples and in your own words write down some benefits and trade-offs of using encapsulation in classes: +# +# +# + +# Stretch Task +# Make the name property private, and add a get_name() method to make it read only. diff --git a/sprint-5/09-encapsulation.txt b/sprint-5/09-encapsulation.txt deleted file mode 100644 index a5550d701..000000000 --- a/sprint-5/09-encapsulation.txt +++ /dev/null @@ -1,8 +0,0 @@ -Read through Python encapsulation -https://www.w3schools.com/python/python_encapsulation.asp -and think about some of the benefits that encapsulation can add to a class. - -Do some further research of your own to learn about encapsulation. - -Think of some examples and in your own words write down some benefits and trade-offs of using encapsulation in classes: - From 6d29a8c4d74cd20a3d9a86fcc14dd3afe6bd33e1 Mon Sep 17 00:00:00 2001 From: l Date: Wed, 30 Sep 2026 12:37:28 +0100 Subject: [PATCH 12/13] language pass 2 --- sprint-5/08-implement.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sprint-5/08-implement.py b/sprint-5/08-implement.py index 3b4f6540f..f8145e538 100644 --- a/sprint-5/08-implement.py +++ b/sprint-5/08-implement.py @@ -1,10 +1,10 @@ class Person: - def __init__(self, name: str, age: int, preferred_operating_system: str): + def __init__(self, name: str, age: int, preferred_car_brand: str): self.name = name self.age = age - self.preferred_operating_system = preferred_operating_system + self.preferred_car_brand = preferred_car_brand -imran = Person("Imran", 22, "Ubuntu") +imran = Person("Imran", 22, "Mercedes") print(imran.is_adult()) # Task: From b79490c97060c84764755cc200428519c6f61810 Mon Sep 17 00:00:00 2001 From: l Date: Wed, 30 Sep 2026 13:16:38 +0100 Subject: [PATCH 13/13] make the instructions consistent with curriculum --- sprint-5/01-predict.py | 1 + sprint-5/02-playcomputer.js | 4 +++- sprint-5/03-fix.py | 8 ++++---- sprint-5/04-addmypy.py | 12 +++++------- sprint-5/05-predict.py | 10 +++++++--- sprint-5/06-classes.py | 9 ++++----- sprint-5/07-methods.txt | 3 ++- sprint-5/08-implement.py | 34 ++++++++++++++++------------------ sprint-5/09-encapsulation.py | 29 ++++++++++++++--------------- sprint-5/10-dataclass.py | 16 +++++++++------- sprint-5/11-predict.py | 5 +++++ sprint-5/12-fix.py | 34 +++++++++++++++------------------- sprint-5/13-refactor.py | 24 ++++++++++-------------- sprint-5/14-implement.py | 8 +++++--- sprint-5/15-analyse.py | 2 +- sprint-5/16-playcomputer.py | 4 ++-- 16 files changed, 103 insertions(+), 100 deletions(-) diff --git a/sprint-5/01-predict.py b/sprint-5/01-predict.py index fca6e4b1a..5c9b9105e 100644 --- a/sprint-5/01-predict.py +++ b/sprint-5/01-predict.py @@ -7,6 +7,7 @@ def double(value): def second(value): return value[1] +# TASK 1: # 1. Predict what you think will happen with each of the following functions # 2. Then, test it, and explain in your own words what is actually happening and why # (feel free to comment out lines if you think they cause errors or crashes while testing) diff --git a/sprint-5/02-playcomputer.js b/sprint-5/02-playcomputer.js index 2fad07c1c..b0bc1c733 100644 --- a/sprint-5/02-playcomputer.js +++ b/sprint-5/02-playcomputer.js @@ -24,4 +24,6 @@ rl.question("What URL should we fetch?\n> ", async (url) => { rl.close(); }); -// Task: Leave a comment on any line that you can see has some errors explaining what you think the problem is +// TASK 2 +// Read through this file and predict what it does. +// Leave a comment on any lines if you spot any errors, offering an explanation of the problem. diff --git a/sprint-5/03-fix.py b/sprint-5/03-fix.py index 477b11f43..3a558945c 100644 --- a/sprint-5/03-fix.py +++ b/sprint-5/03-fix.py @@ -3,7 +3,7 @@ def double(number): print(double(10)) -# Task: -# What is the bug here? -# How could you fix it? -# Are there multiple possible ways to fix it? +# TASK 3: +# Read the code and see if you can find any bugs. +# Write down what the bug is, and how would you fix it? +# Are there multiple ways you could fix it? diff --git a/sprint-5/04-addmypy.py b/sprint-5/04-addmypy.py index e1abe7c45..57f00cfee 100644 --- a/sprint-5/04-addmypy.py +++ b/sprint-5/04-addmypy.py @@ -29,11 +29,9 @@ def format_pence_as_string(total_pence): print(f"The bank accounts total {total_string}") -# TASK +# TASK 4 # This code contains bugs related to types. They are bugs mypy can catch. -# -# 1. Read this code to understand what it's trying to do. -# 2. Install and set up mypy in a python virtual environment -# 3. Add type annotations everywhere appropriate -# 4. Run `mypy 04-addmypy.py`, and fix any errors -# 5. When you're confident all of the type annotations are correct, and the bugs are fixed, run the code and check it works. +# Read this 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. diff --git a/sprint-5/05-predict.py b/sprint-5/05-predict.py index cc15650d2..a66f71253 100644 --- a/sprint-5/05-predict.py +++ b/sprint-5/05-predict.py @@ -14,7 +14,11 @@ print(imran["name"]) print(imran["address"]) -# Task: -# Try running mypy on this file and see what happens -# Predict what do you think will happen when you run this task? +# TASK 5: +# This code contains some untyped objects. +# Try checking it with mypy before running the code and predict what you think will happen when you run the code. +# +# Prediction: +# # Can you explain what actualy happens? +# diff --git a/sprint-5/06-classes.py b/sprint-5/06-classes.py index ff335765d..9f0814104 100644 --- a/sprint-5/06-classes.py +++ b/sprint-5/06-classes.py @@ -12,17 +12,16 @@ def __init__(self, name: str, age: int, preferred_operating_system: str): print(eliza.name) print(eliza.address) -# Task 6.1: -# Run mypy on this code -# Fix the code so there are no errors in mypy or when it runs +# TASK 6.1: +# Run mypy and fix any errors. -# Task 6.2: +# TASK 6.2: # Create a new function in this file called likes_apple # It should take a person as parameter # It returns true if the preferred operating system is "macOS" or "iOS" # It should return false for any other preferred os # Add all the appropriate type annotations and test it has no errors in mypy -# Task 6.3: +# TASK 6.3: # Compare objects and classes # What are some advantages and disadvantages of each? diff --git a/sprint-5/07-methods.txt b/sprint-5/07-methods.txt index 28098862a..13325e967 100644 --- a/sprint-5/07-methods.txt +++ b/sprint-5/07-methods.txt @@ -1,4 +1,5 @@ -Answer the following question: +TASK 7: +Answer the following questions: What is the difference between a method and a function? Can you give some advantages of methods over functions? diff --git a/sprint-5/08-implement.py b/sprint-5/08-implement.py index f8145e538..f29a5ddff 100644 --- a/sprint-5/08-implement.py +++ b/sprint-5/08-implement.py @@ -4,27 +4,25 @@ def __init__(self, name: str, age: int, preferred_car_brand: str): self.age = age self.preferred_car_brand = preferred_car_brand +def drivers_license_check(person: Person): + if person.is_adult() == True: + return 'Valid drivers license' + + return 'This person is underage!' + imran = Person("Imran", 22, "Mercedes") -print(imran.is_adult()) +print(drivers_license_check(imran)) # should return 'Valid drivers license' -# Task: -# 1. Add the `drivers_license_check` free function and the `is_adult` method into the code -# Make sure your code currently gives the expected output. -# -# 2. Change the `Person` class to take a date of birth -# Use the standard library's `datetime.date` class -# https://docs.python.org/3/library/datetime.html#datetime.date -# Store the `date of birth` in a field instead of `age` (it should be a `str`) +# TASK 8: # -# 3. Try to run your code -# How does this change break your code. -# What kind of error do you get? -# Is it helpful in identifying where your next change needs to be? -# Type your thoughts here: +# Add an `is_adult` method into the class, and make sure your code gives the expected output. # +# Change the `Person` class to take a date of birth using +# the standard library's `datetime.date` class +# https://docs.python.org/3/library/datetime.html#datetime.date)) +# and store the `date of birth` instead of `age`. # +# Try to run your code now and observe how this change breaks your code. +# What kind of error do you get? Is it helpful in identifying where your next change needs to be? # -# -# 4. Update the `is_adult` method so the error is fixed. -# Using the `drivers_license_check` function check everything runs as expected - +# Now update only the `is_adult` method to fix the error and check everything works correctly. diff --git a/sprint-5/09-encapsulation.py b/sprint-5/09-encapsulation.py index a9d47561a..115c01c90 100644 --- a/sprint-5/09-encapsulation.py +++ b/sprint-5/09-encapsulation.py @@ -1,3 +1,17 @@ +# TASK 9 +# +# Having done some research on encapsulation, think about the benefits. +# +# Think of some examples and in your own words write down some benefits and trade-offs of using encapsulation in classes: +# +# +# + +# STRETCH TASK 9.1 +# +# In the following class, make the name property private +# Add a `get_name()` method to allow read-only access. + class Person: def __init__(self, name: str, age: int): self.name = name @@ -17,18 +31,3 @@ def is_adult(self): print(eliza.age) # fails print(imran.__age) # fails print(eliza.is_adult()) # works and prints False - -# Task 9 -# Read through Python encapsulation -# https://www.w3schools.com/python/python_encapsulation.asp -# and think about some of the benefits that encapsulation can add to a class. -# -# Do some further research of your own to learn about encapsulation. -# -# Think of some examples and in your own words write down some benefits and trade-offs of using encapsulation in classes: -# -# -# - -# Stretch Task -# Make the name property private, and add a get_name() method to make it read only. diff --git a/sprint-5/10-dataclass.py b/sprint-5/10-dataclass.py index 857ef3379..3847d07d8 100644 --- a/sprint-5/10-dataclass.py +++ b/sprint-5/10-dataclass.py @@ -1,12 +1,14 @@ -# Task 9.1: +# TASK 10.1: # Copy the code you have so far from task 08-implement.py -# Convert this to a dataclass -# Test that the dataclass works with mypy, with equality checks +# Convert your existing `Person` class from task 8 into a value type using `@datatype` +# so you can print the class (and see it's type and properties) +# and compare class instances that are identical. +# Make sure your `is_adult` method and `drivers_license_check` free function both work as normal. -# Task 9.2: -# Add a greeting method that says "Hello, !" +# TASK 10.2: +# Make a new method on your Person class - `greet()` which should return `"Hello !"` when used. -# Task 9.3: +# TASK 10.3: # Read the @datatype documentation here: https://docs.python.org/3/library/dataclasses.html -# Explain what does `frozen=True` do to the class? +# Explain what `frozen=True` does to the class? # What other options could you play around with and explore? Offer suggestions for any that would be useful here. diff --git a/sprint-5/11-predict.py b/sprint-5/11-predict.py index f4a639b52..b022f3b43 100644 --- a/sprint-5/11-predict.py +++ b/sprint-5/11-predict.py @@ -28,3 +28,8 @@ def print_family_tree(family: FamilyTree): print(f"{child.name} ({child.age} years old)") print_family_tree(family) + +# TASK 11 +# There is a bug in this code. Can you spot it? +# Run your code through mypy. Does mypy spot it? +# Offer an explanation for what is happening. diff --git a/sprint-5/12-fix.py b/sprint-5/12-fix.py index bbeefe60a..c1b35296f 100644 --- a/sprint-5/12-fix.py +++ b/sprint-5/12-fix.py @@ -34,30 +34,26 @@ def print_tree(self): family_tree.print_tree() species_tree.print_tree() -# Task 11: -# Experiment with mypy and make sure that the family tree only takes `Person` types and the species tree only takes `Animal` types. -# -# We are going to add printing to the above code. +# TASK 12: +# We are going to improve the printing in the above code, you can find a copy in `12-fix.py`. # -# Unlike our earlier example, we want to avoid having to create two -# separate looping methods to print out an entire tree for each datatype. -# So we have created a single looping function within Tree that will work for any datatype. +# Experiment with mypy and make sure that the family tree only takes `Person` types and the species tree only takes `Animal` types. # -# Currently the `Tree.print_tree()` function doesn't do anything. +# Currently the `Tree.print_tree()` method doesn't look very pretty. # -# Add some appropriate methods to each of the Animal and Person classes to allow it to work. +# Change only the Animal and Person classes to allow the `Tree.print_tree()` method to display an output that looks like this: # -# **Stretch task** +# ``` +# Imran (30 years old) +# - Fatma (4 years old) +# - Aisha (6 years old)) +# Mammals (Variable size) +# - Cat (Small size) +# - Dog (Medium size) +# ``` + +# STRETCH TASK 12.2 # # Think of another type of data that can be organised into a tree. # # Add a new class for this, instantiate some variables, and have the existing `Tree` class print it out. -# Here is an example of what should be printed out -''' -Imran (30 years old) -- Fatma (4 years old) -- Aisha (6 years old)) -Mammals (Variable size) -- Cat (Small size) -- Dog (Medium size) -''' diff --git a/sprint-5/13-refactor.py b/sprint-5/13-refactor.py index 9bf8be375..4050324cf 100644 --- a/sprint-5/13-refactor.py +++ b/sprint-5/13-refactor.py @@ -41,17 +41,13 @@ def find_possible_laptops(laptops: List[Laptop], person: Person) -> List[Laptop] possible_laptops = find_possible_laptops(laptops, person) print(f"Possible laptops for {person.name}: {possible_laptops}") -# Task 12 -#Try changing the type annotation of `Person.preferred_operating_system` from `str` to `List[str]`. -# -#Run mypy on the code. -# -#It tells us different places that our code is now wrong. Fix it to remov eany errors. -# -#Now we changed the types, we probably also want to _rename_ our fields to something appropriate. -# -#Run mypy again. -# -#Fix all of the places that mypy tells you need changing. -# -#Then, make sure the program works as you'd expect. +# Task 13 +# Change the type annotation of `Person.preferred_operating_system` from `str` to `List[str]`. +# Run mypy on the code. +# It tells us different places that our code is now wrong. Fix it to remove any errors. + +# Now we changed the types, we probably also want to _rename_ our field to something appropriate. +# Run mypy again. + +# Fix all of the places that mypy tells you need changing. +# Then, make sure the program works as you'd expect. diff --git a/sprint-5/14-implement.py b/sprint-5/14-implement.py index 61bf1fbfc..75a9bba7b 100644 --- a/sprint-5/14-implement.py +++ b/sprint-5/14-implement.py @@ -41,14 +41,16 @@ def find_possible_laptops(laptops: List[Laptop], person: Person) -> List[Laptop] possible_laptops = find_possible_laptops(laptops, person) print(f"Possible laptops for {person.name}: {possible_laptops}") -# Task 13 -# It currently handles operating systems as strings. +# TASK 14 +# The above code currently handles operating systems as strings. # # Refactor the code to use enums for operating systems. # # Check with mypy and test it to ensure the program still works correctly. # -# Replace the list of existing people with the `input` function to read a person's name, age, and preferred operating system. +# Replace the list of existing people with the `input` function +# https://docs.python.org/3/library/functions.html#input +# to read a person's name, age, and preferred operating system. # # Make sure your implementation has a good user experience, and properly validates the inputs, mapping an OS to one of the enum values. # diff --git a/sprint-5/15-analyse.py b/sprint-5/15-analyse.py index 908db405e..2204fb934 100644 --- a/sprint-5/15-analyse.py +++ b/sprint-5/15-analyse.py @@ -71,7 +71,7 @@ def max_gap_between_values(self) -> Optional[int]: print(unsorted_values.max_gap_between_values()) # This doesn't work - the superclass doesn't define this method. -# Task 14 +# TASK 15 # Try using this code and make sure you understand how it works and what it does # # Answer the following questions, writing your answers in the file, before checking the answers. diff --git a/sprint-5/16-playcomputer.py b/sprint-5/16-playcomputer.py index 0616c8761..20197341b 100644 --- a/sprint-5/16-playcomputer.py +++ b/sprint-5/16-playcomputer.py @@ -22,8 +22,8 @@ def get_full_name(self) -> str: suffix = f" (née {self.previous_last_names[0]})" return f"{self.first_name} {self.last_name}{suffix}" -# Task: -# Play computer here +# TASK 16: +# Play computer with this code # Describe what is happening and why on each line below here # If any lines cause errors, comment out the line and explain why the error happens