Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Open In Colab Open In Kaggle

Exercise 1: A class with state and behaviour

Define a Glacier class with name and area_km2 instance attributes, a describe() method returning a short string, and a __repr__. Create one and print both the object and its description.

Exercise 2: Instance versus class attributes

Define a Planet class with a shared class attribute g_earth = 9.81 and per-instance attributes name and surface_gravity. Create two planets and show that g_earth is shared while surface_gravity differs.

Exercise 3: A dataclass record

Use @dataclass to define a Reading with timestamp (str) and temp_celsius (float). Show that two readings with equal fields compare equal, and print one to see the generated repr.

Exercise 4: Read and mutate methods, with validation

Define a Series class holding a list of floats, with add(x) to append and mean() to return the average. mean() should raise ValueError if there are no values. Demonstrate both.

Exercise 5: Composition

Define a Catchment class that holds several Series objects keyed by name (composition). Add an add_series(name, series) method and a names() method returning the keys. Build one with two series.

Exercise 6: Fix the shared class-level list

The class below shares one list across all instances. Rewrite it so each instance has its own entries, then show two loggers do not contaminate each other.

class Logger:
    entries = []
    def __init__(self, name):
        self.name = name
    def log(self, msg):
        self.entries.append(msg)

Exercise 7: Inheritance and overriding

Define a base Station with a name attribute and a kind() method returning "station". Define RiverGauge(Station) that overrides kind() to return "river gauge". Show that a RiverGauge keeps the inherited name but reports the new kind.

Exercise 8: An invariant with assert

Write normalise(weights) that divides each weight by the total, and asserts the invariant that the result sums to 1 (within a small tolerance). Test it on [2.0, 2.0].

Exercise 9: A custom exception

Define NegativeDischargeError(ValueError) and a function check(q_m3s) that raises it when discharge is negative. Catch it and print the message.

Exercise 10: Try / except / else / finally

Write safe_divide(a, b) that returns a / b, catches ZeroDivisionError (returning None), prints a message in the else branch when it succeeds, and always prints “done” in finally. Call it with (6, 2) and (6, 0).

Exercise 11: Validate preconditions

Write validate(temp_celsius, rh_percent) that raises ValueError if the temperature is below absolute zero or the relative humidity is outside 0–100 %. Show it passing on valid input and raising on rh_percent = 150.

Exercise 12: Logging with levels

Configure logging to stdout at INFO level, get a logger, and emit a debug message (which should be suppressed), an info message, and a warning.

Exercise 13: Write and run a pytest suite

Write a module mymod.py with double(x) returning 2 * x, and a parametrised test file that checks three cases. Run pytest on it with a subprocess and print the output.

Exercise 14: Replace assert-based validation

The line assert rh_percent >= 0, "negative humidity" disappears under python -O. Rewrite the check as a function that raises ValueError, so it fires regardless of optimisation. Demonstrate on a valid value.

Exercise 15: Validated earthquake events — a real dataset

The USGS publishes a feed of every earthquake it records; this exercise reuses the archived one-month extract from 1.5’s pandas exercises.

Combine both halves of this subchapter: a class that carries state, and exceptions that refuse to let it be built from bad data.

Step 1. Read quakes_path with pandas, keeping only the place, mag, and depth columns. Print the resulting shape.

Step 2. Define an EarthquakeEvent class with instance attributes place: str, magnitude: float, and depth_km: float; a __repr__ showing all three; and a method is_shallow(self, threshold_km=70.0) -> bool returning whether depth_km is below the threshold. In __init__, raise ValueError if magnitude or depth_km is negative — a real event never has either, and this is external data, not your own arithmetic, so it is an exception, not an assert.

Step 3. Confirm the validation works: build one valid EarthquakeEvent and print it, then show that EarthquakeEvent("test", -1.0, 10.0) raises ValueError.

Step 4. Loop over the three columns from Step 1 with zip, building one EarthquakeEvent per row inside a try/except ValueError. Collect the successfully built events in a list, and count how many rows were rejected instead of stopping the loop.

Step 5. Using is_shallow(), count how many events in your list are shallow. Separately, find the single deepest event with an explicit loop that tracks a running “deepest so far” variable — the same pattern StationNetwork.coldest_station uses in the lecture, not max().

Step 6. Print a short summary: the number of events built, the number of rows rejected, the number of shallow events, and the deepest event’s repr.

Exercise 16: The solar system, continued

Exercise 17 of 1.2 left the solar system as a dictionary: planet_masses_1e24kg, mapping a name to a mass in units of 10^24 kg. A dictionary keeps the name and the mass together only by convention — pass the mass somewhere on its own and the name is gone, which is exactly what went wrong in that exercise’s bonus question.

Now that classes are available, finish the job: give a planet a type, so that its name and its mass travel as one object and the comparison you want to make becomes a method on it.

The dictionary is rebuilt below so this exercise stands on its own.

Q12) Write a class Planet with two attributes, name and mass_1e24kg.

Create one instance for Earth (5.97) and one for Jupiter (1898), then print each one’s name and mass to check they were stored.

Hint: give __init__ type annotations, as 1.7’s lecture does — name: str, mass_1e24kg: float. They are not enforced at runtime, but they are the only place the unit is written down.

Q13) Add a method is_light that compares a planet against Jupiter.

It should report True when the planet is strictly lighter than Jupiter, False when it is strictly heavier, and the string "same mass!" when the two are equal. Ask it whether Earth is lighter than Jupiter, and whether Jupiter is lighter than itself.

Q14) Now fix what Q13 asked you to build.

The method you just wrote has two problems that 1.7’s own material names directly.

  1. It prints its answer instead of returning it, so a caller cannot use the result — if earth.is_light(): will not do what you expect.

  2. It returns a bool on two branches and a str on the third, so the caller has to test the type of the answer before trusting it.

Rewrite it as compare_to_jupiter() returning one of three strings — "lighter", "heavier", "same" — and have the caller do the printing. Then loop over every planet in planet_masses_1e24kg, build a Planet for each, and print the verdict for all eight.

Q15) Give the class the invariant it is missing.

A negative mass is not a planet. Raise a ValueError from __init__ when mass_1e24kg is not strictly positive, and show both that a valid planet still constructs and that Planet("nonsense", -1.0) fails. Add a __repr__ so a Planet prints readably.

This is the same shape as Exercise 15’s EarthquakeEvent: validate at construction, so that every object that exists is one you can trust.