1 Setting up Python

At the time of writing, the latest stable release of Python is version 3.10.5. If you already have Python installed, please ensure that you have at least version 3.6 or newer.

To install or upgrade Python, you can either:

  • download and run the latest installer from https://www.python.org (relevant for Windows and macOS)
  • use a package manager (relevant if you are using Linux, or the Homebrew package manager on macOS)

You can verify whether Python was successfully installed on your system by opening your Terminal or PowerShell and entering the command python --version or python3 --version.

In addition to installing Python, you will also need a text editor. Here is a non-exhaustive list of suggestions:

  • Mu: a simple Python editor for beginner programmers (limited to Python but very simple to the point of being suitable for children)
  • Geany: a lightweight Integrated Development Environment (IDE) supporting over 50 programming languages, including Python
  • VSCodium: the open-source version of Microsoft Visual Studio Code, i.e. a very similar product but without telemetry built-in. Both editors are fully-featured and contain many creature comforts for advanced programmers. However, the wealth of options may be overwhelming to beginners.
  • Long-established text editors such as Vim or Emacs (only use if you are already familiar with them due to the steep learning curve)

2 Running Python: Hello World!

A traditional first program to run is “Hello World!”. We will use this to ensure that everyone has Python properly installed and can execute Python scripts.

print("Hello World!")
## Hello World!

We can directly launch Python from our command line (Terminal or PowerShell) and execute the program interactively from there, or save the program to a file (e.g. called hello.py) and execute the program stored within the file by running Python on it: python3 hello.py. For the latter option, we need our Terminal to have the folder in which we saved the script file as its working directory. We can set the working with the cd command followed by a space and the file path. (NB: You may also be able to drag the folder to the Terminal after having typed cd followed by a space.) You can verify the path with the command pwd (“print working directory”) and/or list the content of the directory with ls(macOS/Linux) or dir (Windows) to see whether your script file appears in the list of contents.

A further option is to make our Python script executable. For this, we place a line that will tell our system how our script should be executed. NB: The path provided explicitly applies to Unix-like systems (Linux and macOS among others) but should be also interpretable (or even unnecessary) on Windows.

#!/usr/bin/env python3
print("Hello World!")

On Unix-like systems, we should then give executable permissions to the file with chmod +x hello.py. Once this is done, we can run the script as ./hello.py.

3 Python basics: A (hopefully) gentle introduction

Let us start with a “cultural studies flavoured” approach: We will generate the American song 99 bottles of beer of the wall in Python to see some of the basic concepts of the language. We will be aiming for the following lyrics:

99 bottles of beer on the wall, 99 bottles of beer. Take one down and pass it around, 98 bottles of beer on the wall.

[…]

1 bottle of beer on the wall, 1 bottle of beer. Take one down and pass it around, no more bottles of beer on the wall.

No more bottles of beer on the wall, no more bottles of beer. Go to the store and buy some more, 99 bottles of beer on the wall.

3.1 What do we need to do to generate this song?

  • Count down from 99 to 0
  • Use the appropriate plural or singular of the noun bottle
  • Write 0 as “No more” or “no more”
  • Handle the ending, which differs from the rest of the song
  • Export the result to a file
  • Extras:
    • Make alternate versions: switch the beverage and the starting number
    • Run the script with alternations from the command line
  • Luxury extras (challenges for when you are more familiar with Python):
    • Further alternations: switch the container
    • Spell out the numbers

3.2 How do we count down from 99?

We can repeat operations for a certain number of times using a for loop. At each repetition, a specified variable (often called i for iterative) is assigned a different value. To print numbers from 1 to 10, we can do the following:

for i in range(1, 11):
    print(i)
## 1
## 2
## 3
## 4
## 5
## 6
## 7
## 8
## 9
## 10

Notice how the count is one number short of the endpoint of the range.

The range() function may take a further argument specifying the interval to be used. We could skip even numbers by specifying 2 as an interval.

for i in range(1, 11, 2):
    print(i)
## 1
## 3
## 5
## 7
## 9

To count down, we can specify the interval negatively. We also don’t have to stick with i for the name of the iterating variable, but can choose something more expressive.

for bottle_count in range(99, -1, -1):
    print(bottle_count)

3.3 How do we produce the appropriate plural or singular form?

Our condition is that we only need the singular form when the number of bottles is 1. We can achieve this with a conditional if statement.

if bottle_count == 1:
    print(str(bottle_count) + " bottle")
else:
    print(str(bottle_count) + " bottles")

There are more elegant ways to insert variables into strings:

print("%s bottles" % bottle_count) # "old school" way
print(f"{bottle_count} bottles") # new f-string introduced in Python 3.6

We can now combine our for-loop and our if-statement:

for bottle_count in range(99, -1, -1):
    if bottle_count == 1:
        print(f"{bottle_count} bottle")
    else:
        print(f"{bottle_count} bottles")

3.4 Add the rest of the song

for bottle_count in range(99, -1, -1):
    if bottle_count == 1:
        print(f"{bottle_count} bottle of beer on the wall, {bottle_count} bottle of beer.\n\tTake one down and pass it around, no more bottles of beer on the wall.\n")
    else:
        print(f"{bottle_count} bottles of beer on the wall, {bottle_count} bottles of beer.\n\tTake one down and pass it around, {bottle_count - 1} bottles of beer on the wall.\n")

It is generally advised to keep lines of code under 80 characters for better readability. But how can we keep the line short when our text is long? While this length limit is more a suggestion than a requirement, we can still comply with it by splitting parts of the string as long as they are within brackets. NB: Do not place any commas between the strings as Python would interpret this as a tuple, i.e. a data type akin to a list that cannot be edited.

for bottle_count in range(99, -1, -1):
    if bottle_count == 1:
        print(
            f"{bottle_count} bottle of beer on the wall, "
            f"{bottle_count} bottle of beer.\n\t"
            "Take one down and pass it around, "
            "no more bottles of beer on the wall.\n"
        )
    else:
        print(
            f"{bottle_count} bottles of beer on the wall, "
            f"{bottle_count} bottles of beer.\n\t"
            "Take one down and pass it around, "
            f"{bottle_count - 1} bottles of beer on the wall.\n"
        )

3.5 How do we write out 0 as “no more”?

There are multiple ways to achieve this. We could handle this with an if-statement, but it would quickly become convoluted due to the existing if-statement for handling the singular, and the fact that 0 occurs both when the bottle count is 1 due to the subtraction, and in the final line. A more elegant solution is to substitute the character 0 with the character string “no more”. For this, we use regular expressions, for which we need to import the module re. The imported modules are typically listed at the very top of a Python script. We can even use regular expressions to get rid of the if-statement to handle the plural-singular distinction and reserve the conditional to handle the last line. To substitute the text we want, we first store a default version of the line and then perform the substitutions we want on it.

Some things to note:

  • If we just substitute “0 bottles” with “no more bottles”, we will end up with “9no more bottles, 8no more bottles”, etc. We therefore need to specify that the 0 should be preceded by a word boundary. The regular expression wild card for word boundaries is \b. In Python, this particular wild card is somewhat confusing because the sequence \b is an escape sequence for bytes literals, so we either need to escape the \ escape character itself, hence \\b, or use a raw string notation r"\b". In a nutshell, raw strings ignore any Python-specific uses of the backslash character and pass everything as is to the regular expression parser. If you are confused, you can place double backslashes for any wild card, whether they are necessary (e.g. \\b, \\1) or not (e.g. \w or \\w). You can also play around with the raw string notation to see whether you prefer it (e.g. r"\b", r"\1", r"\w").
  • To have a capitalized “N” when “no more” occurs at the beginning of a line, we can use the anchor ^ to specify the beginning of the string.
  • If we just substitute “1 bottles” with “1 bottle”, we will end up with “91 bottle, 81 bottle” etc. We therefore specify that the 1 should be preceded by a word boundary.
import re

for bottle_count in range(99, -1, -1):
    default_line = (
        f"{bottle_count} bottles of beer on the wall, "
        f"{bottle_count} bottles of beer.\n\t"
        "Take one down and pass it around, "
        f"{bottle_count - 1} bottles of beer on the wall.\n"
    )
    updated_line = re.sub("\\b0", "no more", default_line)
    updated_line = re.sub("^n", "N", updated_line)
    updated_line = re.sub("\\b1 bottles", "1 bottle", updated_line)
    print(updated_line)

3.6 How do we handle the ending?

Our song looks fine except for the ending. We can use an if-statement to treat the ending differently. We can now also skip the capitalization of “no” at line beginnings.

import re

for bottle_count in range(99, -1, -1):
    if bottle_count > 0:
        default_line = (
            f"{bottle_count} bottles of beer on the wall, "
            f"{bottle_count} bottles of beer.\n\t"
            "Take one down and pass it around, "
            f"{bottle_count - 1} bottles of beer on the wall.\n"
        )
        updated_line = re.sub("\\b0", "no more", default_line)
        updated_line = re.sub("\\b1 bottles", "1 bottle", updated_line)
    else:
        updated_line = (
            "No more bottles of beer on the wall, "
            "no more bottles of beer.\n\t"
            "Go to the store and buy some more, "
            "99 bottles of beer on the wall.\n"
        )
    print(updated_line)

Alternatively, we can also let the for-loop stop at 1 and just write the ending after the loop.

import re

for bottle_count in range(99, 0, -1):
    default_line = (
        f"{bottle_count} bottles of beer on the wall, "
        f"{bottle_count} bottles of beer.\n\t"
        "Take one down and pass it around, "
        f"{bottle_count - 1} bottles of beer on the wall.\n"
    )
    updated_line = re.sub("\\b0", "no more", default_line)
    updated_line = re.sub("\\b1 bottles", "1 bottle", updated_line)
    print(updated_line)

print(
    "No more bottles of beer on the wall, "
    "no more bottles of beer.\n\t"
    "Go to the store and buy some more, "
    "99 bottles of beer on the wall.\n"
)

3.7 A different way of looping

In addition to a for-loop, we could also use a while-loop. Unlike in for-loops, there is no automatic iteration of the looping variable, so we have to be extra careful to specify how the loop should end or we may be stuck in an infinite loop. In general, prefer for-loops over while-loops unless there is a good reason to use a while-loop.

import re

bottle_count = 99

while bottle_count > 0:
    default_line = (
        f"{bottle_count} bottles of beer on the wall, "
        f"{bottle_count} bottles of beer.\n\t"
        "Take one down and pass it around, "
        f"{bottle_count - 1} bottles of beer on the wall.\n"
    )
    updated_line = re.sub("\\b0", "no more", default_line)
    updated_line = re.sub("\\b1 bottles", "1 bottle", updated_line)
    print(updated_line)
    bottle_count = bottle_count - 1 # infinite loop without this line ! ! !

print(
    "No more bottles of beer on the wall, "
    "no more bottles of beer.\n\t"
    "Go to the store and buy some more, "
    "99 bottles of beer on the wall.\n"
)

3.8 Saving the output to a file

There are different ways we can write the output to a file:

  • We can add each line to the output file as it is generated.
import re

for bottle_count in range(99, 0, -1):
    default_line = (
        f"{bottle_count} bottles of beer on the wall, "
        f"{bottle_count} bottles of beer.\n\t"
        "Take one down and pass it around, "
        f"{bottle_count - 1} bottles of beer on the wall.\n\n"
    )
    updated_line = re.sub("\\b0", "no more", default_line)
    updated_line = re.sub("\\b1 bottles", "1 bottle", updated_line)
    with open("99bottles.txt", "a") as outp:
        outp.write(updated_line)
with open("99bottles.txt", "a") as outp:
    outp.write(
        "No more bottles of beer on the wall, "
        "no more bottles of beer.\n\t"
        "Go to the store and buy some more, "
        "99 bottles of beer on the wall.\n\n"
    )
  • Or we can add each line to a string variable and write the entire song to the output file in one go.
import re

song_lyrics = ""

for bottle_count in range(99, 0, -1):
    default_line = (