Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ecdeb60
Remove the unnecessary sidebar text
Sep 9, 2026
d1abcff
rework intro to types
Sep 9, 2026
8514728
merge poonam's dataclass change
Sep 16, 2026
a4cead8
update classes
Sep 16, 2026
6c24cf1
update methods
Sep 16, 2026
d8bb2b5
update dataclass task description
Sep 16, 2026
777753f
generics
Sep 23, 2026
ef57493
refactoring
Sep 23, 2026
701e199
enums
Sep 23, 2026
13d87dc
inheritance
Sep 23, 2026
58f189c
add encapsulation task
Sep 30, 2026
ea7472f
add encapsulation stretch task
Sep 30, 2026
615f33c
Spelling pass 1
Sep 30, 2026
85e6ee3
language pass 2
Sep 30, 2026
d4d6774
language pass 3
Sep 30, 2026
eabce4c
Make tasks consistent
Sep 30, 2026
9d1b2b4
Add the encapsulation prep
Sep 30, 2026
675a451
formatting
Sep 30, 2026
39cc20b
LOs
Sep 30, 2026
f83812f
update task text
Sep 30, 2026
7121e12
Merge branch 'main' into lm-sdc-tools-5-rework
LonMcGregor Sep 30, 2026
46bd2a2
Merge branch 'main' into lm-sdc-tools-5-rework
illicitonion Oct 2, 2026
19a988d
Update common-content/en/module/decomposition/classes-and-objects/ind…
LonMcGregor Oct 5, 2026
4bb72da
review types intro
Oct 5, 2026
2109f4a
Merge remote-tracking branch 'refs/remotes/origin/lm-sdc-tools-5-rewo…
Oct 5, 2026
a92f4e6
review example code quality
Oct 5, 2026
c377f97
Update common-content/en/module/decomposition/methods/index.md
LonMcGregor Oct 5, 2026
834020f
Update common-content/en/module/decomposition/enums/index.md
LonMcGregor Oct 5, 2026
7492121
Update common-content/en/module/decomposition/inheritance/index.md
LonMcGregor Oct 5, 2026
6f81dbc
Update common-content/en/module/decomposition/inheritance/index.md
LonMcGregor Oct 5, 2026
aaf08a9
Update common-content/en/module/decomposition/generics/index.md
LonMcGregor Oct 5, 2026
33194eb
Update common-content/en/module/decomposition/inheritance/index.md
LonMcGregor Oct 5, 2026
fc71d54
Update common-content/en/module/decomposition/inheritance/index.md
LonMcGregor Oct 5, 2026
d2b3543
Update common-content/en/module/decomposition/enums/index.md
LonMcGregor Oct 5, 2026
2a363b9
review encapsulation summary
Oct 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 27 additions & 20 deletions common-content/en/module/decomposition/classes-and-objects/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ time = 30
objectives = [
"Describe the purpose of a class.",
"Explain the relationship between a class and instances of that class.",
"Use classes in mypy.",
"Use classes in mypy and python.",
]

[build]
Expand All @@ -31,22 +31,22 @@ eliza = {

This allows us to pass around the values of `imran` or `eliza`, and access all of the related information while we do.

We've also already seen that it is useful to know that you can't call `.lower()` on the value `2`.
We now know that typing can tell us if we make errors like calling `.lower()` on the numeric value `2`.

It would be useful for a type checker to tell us if we try to access a property of an object that that object doesn't have:
It would be useful for a type checker to tell us if we try to access a property of an object that that object doesn't have. Can mypy help us here?

```python
imran = {
"name": "Imran",
"age": 22,
"preferred_operating_system": "Ubuntu",
}
{{<note type="exercise">}}
**Task 5**:

print(imran["name"])
print(imran["address"])
```
Have a look at `05-explain.py`

This code contains some untyped objects.

This code doesn't work, but mypy can't tell us this. As far as it is concerned, a dictionary is a dictionary - it could contain any keys!
Try checking it with mypy before running the code and predict what you think will happen when you run the code.
{{</note>}}

The code in the above exercise doesn't work, but mypy can't tell us this. Remember how we said that type checking has its limits?
As far as mypy is concerned, a dictionary is a dictionary - it could contain any keys!

Instead, we can use a {{<tooltip title="class">}}A class is a template for an object. It lets us say what properties (and methods) all instances of that class will contain.{{</tooltip>}}.

Expand Down Expand Up @@ -78,11 +78,13 @@ This code is saying: "There's a category of object called Person. Every instance

The method called `__init__` is called a constructor - it is what is called when we construct a new instance of the class.

{{<note type="Exercise">}}
Save the above code to a file, and run it through mypy.

Read the error, and make sure you understand what it's telling you.
{{</note>}}
{{<multiple-choice
question="What of the following best describes an 'instance' of a class?"
answers="The variables that are accessed using self, like `self.name` | An object with properties set to values passed into the constructor of a class | The `__init__` method that takes some values as arguments | A description of what a class contains"
feedback=" No, these are called properties | Yes, an instance is an object representing one specific copy of a class | `__init__` is the constructor of a class in python | No, a class already is a description. An instance is more specific."
correct="1" >}}


You can use the names of classes in type annotations just like you can use types like `str` or `int`:

Expand All @@ -94,9 +96,14 @@ print(is_adult(imran))
```

{{<note type="Exercise">}}
Add the `is_adult` code to the file you saved earlier.

Run it through mypy - notice that no errors are reported - mypy understands that `Person` has a property named `age` so is happy with the function.
**Task 6**
Have a look at file `06-classes.py`.

Run mypy and fix any errors.

Add a new function called `likes_apple` which takes a `Person` as parameter and returns true only if the preferred operating system is either `iOS` or `macOS`. Add all the appropriate type annotations and make sure mypy has no errors.

Compare objects and classes and explain some advantages and disadvantages of each.

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.
{{</note>}}
32 changes: 20 additions & 12 deletions common-content/en/module/decomposition/dataclasses/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,23 @@ Our `Person` class is an example of this. We just store some data in it (and may

If a class is just a place to group related data, it is sometimes called a {{<tooltip title="Value object" text="value object">}}A value object is an object which exists just to store data. They are normally immutable (never change).{{</tooltip>}}. We normally consider two value objects to be equal to each other if their fields contain the same values.

There are several functions we can implement on classes that have obvious implementations for value objects.
There are several methods we can implement on classes that have obvious implementations for value objects.

Equality is one: ideally two value objects are the same if their fields are the same. But this is not the case with objects by default:

```python
class Person:
def __init__(self, name: str, age: int, preferred_operating_system: str):
self.name = name
self.age = age
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
```

Similarly, it's useful when we print a value object to see its type and fields. But this is not the case with objects by default:
Similarly, it's useful when we print a value object to see its type and properties. But this is not the case with objects by default:

```python
class Person:
Expand All @@ -48,30 +48,38 @@ imran = Person("Imran", 22, "Ubuntu")
print(imran) # Prints <__main__.Person object at 0x1048b5a90>
```

Python has a useful {{<tooltip text="decorator" title="Decorator">}}A decorator is an annotation you can add to some Python code to give it extra behaviour.{{</tooltip>}} called `dataclass` which generates some of these functions for us. In fact, it even generates the constructor for us.
Python has a useful {{<tooltip text="decorator" title="Decorator">}}A decorator is an annotation you can add to some Python code to give it extra behaviour.{{</tooltip>}} called `dataclass` which generates some of these methods for us. In fact, it even generates the constructor for us.

```python
from dataclasses import dataclass

@dataclass(frozen=True)
class Person:
class Animal:
name: str
species: str
age: int
preferred_operating_system: str
noise: 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')
indigo = Animal("indigo", "cat", 2, "meow") # We can call this constructor - @dataclass generated it for us.
print(indigo) # Prints Animal(name='Indigo', species='cat', age=2, noise='meow')

imran2 = Person("Imran", 22, "Ubuntu")
print(imran == imran2) # Prints True
indigo2 = Animal("indigo", "cat", 2, "meow")
print(indigo == indigo2) # Prints True
```

The `dataclass` decorator generated a constructor, a `__str__` method (which is called when string formatting the value), and a custom `__eq__` method (which is called when comparing two values). This saves us having to write all of that code.

Other languages have a similar idea of a value type, and tools to help make them, such as [Java's record classes](https://docs.oracle.com/en/java/javase/17/language/records.html) and [C#'s' structure types](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct).

{{<note type="exercise">}}
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.
**Task 10**

Work in file `10-implement.py` for this task.

Convert the above `Person` class into a value type using `@dataclass` 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.

Make a new method on your Person class - `greet` which should return `"Hello <person name>!"` when used.

Read the [`@dataclass` documentation](https://docs.python.org/3/library/dataclasses.html). Explain what `frozen=True` would do to the class? What other options could you play around with and explore?
{{</note>}}
120 changes: 120 additions & 0 deletions common-content/en/module/decomposition/encapsulation/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
+++
Comment thread
LonMcGregor marked this conversation as resolved.
title = "Encapsulation"
time = 30
objectives = [
"Define encapsulation.",
"Explain how encapsulation can benefit class design.",
]

[build]
list = "local"
publishResources = false
render = "never"
+++


An advantage of classes over objects is encapsulation.

Imagine you have written your Person class that stores age information.

For privacy reasons, you don't want to reveal the exact age of the person, only whether they are or are not over 18 years old.

This means we need to store the "age" value in a class, but somehow keep it _private_ to that class. The only _public_ information we want is whether or not they are over 18. How can we achieve this?

Look at the following code:

```python
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
```

> [!NOTE]
>
> It is important to be clear about the wording here as there are some subtle differences between fields and properties as used in classes.
> A "field" is the underlying part of a class that stores some value.
> A "property" is the publicly accessible part that you can access from outside the class.
>

In python, any field that begins with two underscores is considered _private_, i.e. it can only be used within that specific class instance.


> [!NOTE]
>
> Using underscores, Python doesn't have a clear way of marking something as private.
> Other programming languages like Java mark this more explicitly with keywords like "private" and "public".
> It's worth becoming familiar with this private/public language even if you're not using it right now.
>

You can now program classes to change behaviour based on the information stored within them.
Compare this with objects, which can only ever store data, and behave the same every time.

Another benefit of encapsulation is letting you make "read only" properties.
Think about the example above.
Imagine you wanted to check if a `Person` class had a certain name using an equality test, but accidentally used a single `=` symbol:
```python
imran.name = "Eliza"
```
Python allows you to update public fields whenever you want.
If `name` were private, and the only way to access it was through a `get_name()` method that returns a string, it would be impossible to accidentally change the value.
In this way, encapsulation can be used to prevent accidental errors in code.

{{<note type="Reading">}}
Read through [Python encapsulation](https://www.w3schools.com/python/python_encapsulation.asp).

Do some further research of your own to learn about encapsulation.
{{</note>}}

{{<note type="exercise">}}
**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 in the file `09-encapsulation.py`

**Stretch Task**

Working in file `09-encapsulation.py`, make the `name` field private, and add a `get_name()` method to allow read-only access.
{{</note>}}

### Why encapsulate?

In your career you will rarely be building code used only once.
It is likely the code you write will sit alongside code written by others as part of a large long-lived codebase.
Classes and encapsulation are really important techniques as you move towards thinking about how others will use your code, and how you plan to make your code maintainable and reusable for future use.

Classes with encapsulation clearly define the outward-facing interface of what you are building.
Think about the documentation you may have read for well-defined APIs like [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) or [argparse](https://docs.python.org/3/library/argparse.html).
You don't need to know how they work internally to make use of them, and the methods and their parameters are clearly stated.
If `argparse` is updated, e.g. to make it more efficient, your code won't break as the public interface won't change.
If `fetch` is changed, e.g. adding a new parameter, type checking will immediately highlight everywhere you need to update your code.

Encapsulation also makes it easy to swap different implementations.
Imagine you started a big project with a python `dict` but later on needed to change it to an [OrderedDict](https://docs.python.org/3/library/collections.html#collections.OrderedDict).
The interfaces are almost exactly the same, so you wouldn't need to change any of the method invocations, making the change much easier and safer.

Encapsulation also helps with testing.
Only the public interface, methods and properties, need to be tested.
You can write the test before you start using test-driven development, defining the public interface and behaviour.
Then you can focus on the implementation inside, and when the test passes you know your class works.
Testing a single class with a well defined interface is much easier than needing to test lots of interconnected separate free functions.

Until now you have been solving small coding challenges with the aim of solving the specific task.
From now on you will start to think more about how you can build a solution that will adapt well to future changes.
Well defined classes that encapsulate your implementations will be a big help.
Loading
Loading