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: Lists — build, modify, slice

Start from the daily mean temperatures [-2.3, -1.1, 0.4, 1.2, -0.8] (°C).

  1. Append a sixth reading, -3.1.

  2. Remove the value 0.4 (the sensor flagged it as unreliable).

  3. Print the first reading, the last reading, and a slice containing the second and third.

  4. Print how many readings remain.

Exercise 2: Dictionaries — build, look up, remove

You are given station records as a list of tuples (code, name, elevation_m):

[("JFJ", "Jungfraujoch", 3571), ("BAS", "Basel-Binningen", 316), ("LUG", "Lugano", 273)]
  1. Build a dict mapping each code to its elevation_m.

  2. Print the elevation for "JFJ".

  3. Look up "ZRH" with .get, returning "unknown" if it is absent.

  4. Delete the entry for "LUG", then print each remaining code and its elevation using .items().

Exercise 3: A pure function

Write a function mean(values) (no type hints) that returns the average of a list of numbers. Test it on [6.1, 7.4, 5.9, 8.2, 6.8], printing the result rounded to two decimals.

Exercise 4: A function with a default argument

Write a function is_warm(temp_celsius, threshold=10.0) (no type hints) that returns True when the temperature is at or above the threshold and False otherwise, using if/else. Test it on 6.1 °C with the default threshold, and again on 6.1 °C with a threshold of 5.0 °C passed by keyword.

Exercise 5: Pair two stations with zip

Two stations recorded the same five days:

dates = ["Mon", "Tue", "Wed", "Thu", "Fri"]
jfj_celsius = [-2.3, -1.1, 0.4, 1.2, -0.8]
bas_celsius = [6.1, 7.4, 5.9, 8.2, 6.8]

Using zip, print one line per day showing the date and the temperature difference (Basel minus Jungfraujoch), rounded to two decimals.

Exercise 6: Number the readings with enumerate

A station wrote one reading per day for a week:

week_celsius = [6.1, 7.4, 5.9, 8.2, 11.3, 9.8, 6.8]
  1. Using enumerate, print one line per reading: the day number and the temperature. Count days from 1, not 0 — enumerate starts at 0, so the day number is the index plus one.

  2. In the same loop, keep track of the warmest reading seen so far and the day it fell on, in two variables defined before the loop. Do not use max.

  3. After the loop, print the warmest reading and its day number.

Exercise 7: while, break, and continue

A logger returned six readings, two of which are missing:

mixed_celsius = [6.1, None, 5.9, None, 9.5, 6.8]

Using a while loop over the indices:

  1. Skip any missing value with continue.

  2. Stop at the first reading above 9.0 °C with break, storing it in first_hot.

  3. Print first_hot after the loop. Define it before the loop so the code is safe even if no reading exceeds the threshold.

Exercise 8: Default arguments and returning two values

Write a function summarise(values, scale=1.0) that multiplies every value by scale and returns two things: the total and the mean.

  1. Call it on [6.1, 7.4, 5.9, 8.2, 6.8] with the default scale, unpacking the result into total_celsius and mean_celsius.

  2. Call it again with scale=2.0 passed by keyword, and print only the mean.

  3. Call it once more and store the result in a single name. Print its type — what did you get?

Exercise 9: A variable number of arguments

Write a function warmest(*temps_celsius) (no type hints) that returns the highest value among any number of positional arguments, using a loop and a running maximum (not max). Test it on three individual readings passed directly, and again by unpacking the list [6.1, 7.4, 5.9, 8.2, 6.8] with *.

Exercise 10: Fix the shared-default bug

An assistant wrote this function; it is correct the first time but wrong on later calls:

def collect_above(values, threshold, out=[]):
    for v in values:
        if v > threshold:
            out.append(v)
    return out

In one comment, explain the bug, then rewrite it so each call starts fresh. Demonstrate with two separate calls, assigning each result to its own variable.

Going deeper (optional)

Exercise 11: Document your function with a docstring

Take the mean(values) you wrote in Exercise 3 and give it a docstring — a one-line summary in triple quotes on the first line of the body. State what it returns and the unit it assumes, so the function carries information the name alone cannot.

Then print mean.__doc__, and call help(mean) to see what a reader of your code would see.

Exercise 12: Tuples — unpacking and immutability

A station’s position is stored as a tuple, station_coords = (46.5475, 7.9853).

  1. Unpack it into lat and lon in one line, and print both.

  2. Try to change the latitude to 47.0. Catch the resulting error with try/except TypeError and print the message.

  3. In a comment, say why a coordinate pair is better stored as a tuple than as a list.

Exercise 13: Means and a filter, with comprehensions

Recreate a stations dict where each code maps to a dict containing readings_celsius:

{"JFJ": {"readings_celsius": [-2.3, -1.1, 0.4, 1.2, -0.8]},
 "BAS": {"readings_celsius": [6.1, 7.4, 5.9, 8.2, 6.8]},
 "LUG": {"readings_celsius": [9.4, 10.2, 8.8, 11.1, 9.9]}}

With a dict comprehension, build means = {code: mean_temp}, calling the mean function you wrote in Exercise 3 rather than summing inline. Then, with a list comprehension, build the list of codes whose mean is below 0 °C.

Exercise 14: Spot the impure function

This function is meant to return the readings without the last one:

def drop_last(readings):
    readings.pop()
    return readings
  1. Call it on original = [6.1, 7.4, 5.9], then print original. What happened?

  2. Rewrite it as a pure function that leaves the caller’s list untouched, and demonstrate that original is unchanged after the call.

  3. In one comment, connect this to the mutable-default bug from the lecture.

Exercise 15: Reject impossible temperatures

Write to_kelvin(temp_celsius) that raises a ValueError when the input is below absolute zero (−273.15 °C) and otherwise returns the temperature in kelvin. Give it a docstring and a type hint.

Loop over [20.0, -300.0, 0.0], calling it inside a try/except so that the invalid value is reported without stopping the loop.

Exercise 16: A year of station data, continued

The optional section ends with Exercise 15. Exercises 16 and 17 are the main exercises of this subchapter.

In subchapter 1.1 you fetched this file, worked out its structure, and converted a single record to °C. You did not yet have the tools to handle all of it at once — now you do.

The goal: turn a year of daily maximum temperatures into a monthly summary, and write that summary out as a new file. The input file has the same three columns as in Exercise 9 of the previous subchapter: the station code, the day as dd.mm.yy, and the daily maximum temperature in degrees Fahrenheit. Your monthly summary will have its own columns: month, n_obs (the number of observations), and mean_temp_celsius.

data cached at: /home/runner/.cache/mlees/station_iib_daily_max_temp_2022.csv

Step 1. Write a function fahrenheit_to_celsius(temp_fahrenheit) that returns the temperature in °C. Test it on the freezing point of water (32 °F should give 0.0 °C) and on 212 °F. A one-line docstring is optional here (see Exercise 11).

You wrote this conversion inline in 1.1. Steps 2 and 4 both need it again; with a function, they call the same tested code instead of repeating the arithmetic.

Step 2. Open the file, read all lines, and loop over the records (skipping the header). For each one, extract the month and the temperature in °C. Print the first three as a check.

Sixteen days in this file have an empty temperature field — a real gap in the station’s record, late October to mid-November. float("") raises ValueError, so your loop has to decide what to do with those days before it can get to the end of the file. Skip them, and count how many you skipped. After the loop, print how many records you converted and how many you skipped; the two should add up to 365.

Step 3. Build a dictionary mapping each month to the number of observations in it.

You do not know in advance which months are present, so the dictionary has to grow as you go. Keep skipping the days with no reading: a day the sensor did not record is not an observation, so October and November should come out short of their full length.

Step 4. Build a second dictionary mapping each month to its mean daily maximum temperature in °C. Print it with each mean rounded to two decimals.

Step 5. Find the warmest month and print it with its mean temperature.

Do this with a loop and a comparison, keeping track of the best value seen so far, as you did for the warmest day in Exercise 6. A dictionary is looked up by key, not by position, so there is no [0] to start from: start from None instead.

Step 6. Write your monthly summary to _files/monthly_means.csv, with the header month,n_obs,mean_temp_celsius and one line per month, creating the _files folder first as you did in 1.1. Then read the file back and print it, to confirm it says what you meant it to say.

Step 7 (optional). This step uses try/except from the going-deeper box in the lecture. Real files contain junk, and you have already met one kind of it — the empty temperature field. Suppose some lines are truncated as well, or hold a temperature that is not a number at all. Modify your reading loop so that it skips any line that does not have three fields or whose temperature will not convert to a number, counting how many lines it skipped.

Test it on this list before applying it to the real file:

test_lines = [
    "IIB,01.01.22,30.2",
    "IIB,02.01.22",
    "IIB,03.01.22,notanumber",
    "IIB,04.01.22,33.4",
]

You should keep 2 and skip 2.

Exercise 17: The solar system

The eight planets and the Moon photographed by spacecraft, arranged diagonally against black

Figure 1:The eight planets and the Moon, as photographed by Mariner 10, Magellan, Galileo, Mars Global Surveyor, Cassini and the Voyager spacecraft. The inner bodies are roughly to scale with each other, as are the outer ones, but the two groups are not to scale with one another. Image NASA/JPL (PIA03153), public domain.

This is the long exercise for this subchapter, and the only one in it whose data is not environmental. Eight planets and their masses are few enough to type by hand, and familiar enough that a wrong answer is easy to spot, which makes them a good first test of lists, dictionaries and functions together.

Work through sections A to C in order: section C writes functions against the dictionary you build in section B. You will pick the exercise up again in 1.7, once classes are available, and turn the dictionary into a Planet type.

The masses come from NASA’s planetary fact sheet. Use units of 10^24 kg throughout, so that Earth is 5.97 and Jupiter is 1898 — the variable names below carry that unit, which is what makes a bare number like 1898 readable three cells later.

A: lists and loops

Q1) Create a list with the names of every planet in the solar system, in order.

Name it planets and use lowercase names, so it starts ["mercury", "venus", ...].

Q2) Have Python tell you how many planets there are by examining your list.

You should get 8. Do not type the number — read it off the list.

Q3) Use slicing to display the first four planets — the rocky ones.

Q4) Iterate through your planets and print the planet name only if it ends in “s”.

You should see venus, mars and uranus, and nothing else.

Hint: the last letter of a name is planet[-1]. Use continue for the names that do not match, so the loop body says explicitly what it skips.

B: dictionary

Q5) Create a dictionary that maps each planet name to its mass.

Call it planet_masses_1e24kg, and read the values off the NASA fact sheet in units of 10^24 kg. Every value has to be in that one unit: entered as 1.898 (in 10^27 kg), Jupiter would come out 1000 times too light in every comparison and ratio below.

Q6) Use your dictionary to look up Earth’s mass.

Q7) Loop through the dictionary and build a list of every planet heavier than 100 x 10^24 kg.

You should end up with ["jupiter", "saturn", "neptune"] — uranus, at 86.8, does not make it.

Hint: .items() gives you the name and the mass together on each pass, as in 1.2’s lecture.

Q8) Add Pluto to your dictionary.

Its mass is 0.0130 x 10^24 kg. Print the names afterwards, looping with .items(), to confirm it landed.

Pluto has not been a planet since 2006. Adding it still takes one assignment, and none of the code that reads the other eight entries has to change.

C: functions

Q9) Write a function that converts a planetary mass to Earth masses.

Jupiter is about 1898 x 10^24 kg, which is about 318 Earth masses:

MJupiter≈1898×1024 kg≈318 MEarthM_{\text{Jupiter}} \approx 1898 \times 10^{24}\,\text{kg} \approx 318\,M_{\text{Earth}}

Call it to_earth_masses(mass_1e24kg) and check it against Jupiter — you should get 317.92294807370183.

Q10) Now write a single function that converts to either Earth masses or Jupiter masses, depending on a keyword argument.

Call it to_planet_masses(mass_1e24kg, reference="jupiter"). Converting Jupiter’s own mass should give 317.923 with reference="earth" and exactly 1.0 with reference="jupiter".

With the default argument, one function covers both conversions. Jupiter in Jupiter masses has to come out as exactly 1.0; any other value means the function divides by the wrong mass.

Q11) Write a function that returns two values.

mass_in_two_units(mass_1e24kg, reference) should return the mass in Earth masses and in masses of whatever reference planet you pass. Test it on Jupiter with reference="mars".

Hint: return a, b returns a tuple, and first, second = f(...) unpacks it — both from 1.2’s lecture.

Bonus. Convert Neptune’s mass to Jupiter masses with your Q10 function. Then pass that result to your Q11 function with reference="jupiter", and compare the second value it returns with the Q10 result. Are they the same? If not, work out which function is being given a number in the wrong unit, and what it should have been given instead — this is the mistake the _1e24kg suffix exists to prevent.