Programming Essentials

In this workshop we cover the building blocks for developing more complex code, looking at

Directing traffic with conditionals

In the first half of this session we’ll look at two types of control flows: conditionals and loops.

Conditionals allow you to put “gates” in your code, only running sections if a certain condition is true. They are common to most programming languages.

In Python, they are called if statements, because you use the if command. For example,

if 5 > 0:
    print("We're inside the if statement")
We're inside the if statement

The line print("We're inside the if statement") will only run if 5 > 0 is true. If not, it’ll get skipped.

Indents are essential. Only indented code will be governed by conditional

if 5 > 0:
    print("We're inside the if statement")

print("This code always runs")
We're inside the if statement
This code always runs

Watch what happens if we change the condition

if 5 > 10:
    print("We're inside the if statement")

print("This code always runs")
This code always runs

Now, the first line doesn’t run. That’s the essence of a conditional.

There’s not much point to using a condition that will always be true. Typically, you’d use a variable in the condition, for example.

pet_age = 10

if pet_age > 10:
    print("My pet is older than 10")

Logical operators

Here is a table of the different operators you can make conditions with. When you run them, they always return either True or False.

Operator True example Description
== 10 == 10 Same value and type
!= "10" != 10 Different value or type
> 10 > 5 Greater than
>= 10 >= 10 Greater than or equal to
< 5 < 10 Less than
<= 5 <= 10 Less than or equal to
in "a" in "apple" First object exists in the second
not in "b" not in "apple" First object does not exist in the second
and 10 == 10 and "a" in "apple" Only true if both conditions are true. Same as &
or 10 == 10 or "b" in "apple" Always true if one condition is true. Same as \|

elif and else

if statements only run if the condition is True. What happens if its False? That’s what the else command is for, it’s like a net that catches anything that slipped past if:

pet_age = 5

if pet_age > 10:
    print("My pet is older than 10")
else:
    print("My pet is 10 or younger")
My pet is 10 or younger

Don’t forget the colon :!

Check what happens when you change the age from 5 to 15.

Finally, what if you wanted to check another condition only if the first one fails? That’s what elif (else-if) is for. It’s another if statement but it only runs if the first fails.

pet_age = 5

if pet_age > 10:
    print("My pet is older than 10")
elif pet_age < 5:
    print("My pet is younger than 5")
else:
    print("My pet is between 5 and 10")
My pet is between 5 and 10

You can include as many as you’d like

pet_age = 5

if pet_age > 10:
    print("My pet is older than 10")
elif pet_age < 5:
    print("My pet is younger than 5")
elif pet_age < 1:
    print("My pet is freshly born")
else:
    print("My pet is between 5 and 10")
My pet is between 5 and 10

Repeat after me

Sometimes you need to repeat a task multiple times. Sometimes hundreds. Maybe you need to loop through 1 million pieces of data. Not fun.

Python’s loops offer us a way to run a section of code multiple times. There are two types: for loops, which run the code once for each element in a sequence (like a list or string), and while loops, which run until some condition is false.

while loops

These are almost the same as if statements, except for the fact that they run the code multiple times. Let’s begin with a basic conditional

number = 5

if number < 10:
    print(str(number) + " is less than 10.")
5 is less than 10.

Using str(number) turns the number into a string, which lets us combine it with ” is less than 10.”

The print statement runs once if the condition is true.

What if we wanted to check all the numbers between 5 and 10? We can use a while loop.

number = 5

while number < 10:
    print(str(number) + " is less than 10.")
    number = number + 1
5 is less than 10.
6 is less than 10.
7 is less than 10.
8 is less than 10.
9 is less than 10.

We’ve done two things

  1. Replace if with while
  2. Introduce number = number + 1 to increase the number each time.

Without step 2, we’d have an infinite loop – one that never stops, because the condition would always be true!

While loops are useful for repeating code an indeterminate number of times.

for loops

Realistically, you’re most likely to use a for loop. They’re inherently safer (you can’t have an infinite loop) and often handier.

In Python, for loops iterate through a sequence, like the objects in a list. This is more like other languages’ foreach, than most’s for.

Let’s say you have a list of different fruit

list_of_fruits = ["apple", "banana", "cherry"]

and you want to run a section of code on "apple", then "banana", then "cherry". Maybe you want to know which ones have the letter “a”. We can start with a for loop

list_of_fruits = ["apple", "banana", "cherry"]

for fruit in list_of_fruits:
    print(fruit)
apple
banana
cherry

This loop’s job is to print out the variable fruit. But where is fruit defined? Well, the for loop runs print(fruit) for every element of list_of_fruits, storing the current element in the variable fruit. If we were to write it out explicitly, it would look like

fruit = list_of_fruits[0]
print(fruit)

fruit = list_of_fruits[1]
print(fruit)

fruit = list_of_fruits[2]
print(fruit)
apple
banana
cherry

Let’s return to our goal: working out which ones have an “a”. We need to put a conditional inside the loop:

list_of_fruits = ["apple", "banana", "cherry"]

for fruit in list_of_fruits:
    if "a" in fruit:
        print("a is in " + fruit)
    else:
        print("a is not in " + fruit)
a is in apple
a is in banana
a is not in cherry

Using range

There’s a special Python object which is useful for loops, the range. This object ‘contains’ all the numbers between a certain range. For example,

range(0,5)
range(0, 5)

cover the numbers \(0-4\), and is somewhat equivalent to [0,1,2,3,4] (for looping purposes). Of course, we can choose a much bigger number:

for i in range(0,1000):
    print(i)

will print every number between \(0\) and \(1000\). This can be useful if you need to loop through multiple objects by indexing.

Looking under the hood: what makes ints ints?

Python is a high level programming language. Its features are often inspired by C and C++, and is itself built in C/C++.

One of the major innovations of C++, and object-oriented programming in general, are classes. We won’t go over how to write your own - it’s beyond the scope of this workshop - but they’re worth a conceptual understanding.

Essentially, all Python variables follow a specific template, known as its class or type. It’s safe to use these interchangebly here. The class is a general template for the variable and it defines what methods (functions) and attributes (variables) live inside the variable.

Inside the variable? Where? Well, a variables contain more than just their value. We use the . operator to access anything besides the value that lives in the variable. For example, all strings have a function called .upper() that makes them uppercase:

random_string = "Hello, this is a sentence."
print(random_string.upper())
HELLO, THIS IS A SENTENCE.

Let me emphasise that all strings have .upper(). That makes upper() a method of strings.

In other words, a class is like an empty form that needs filling. The form string has a field called upper() that is filled with the function as defined above.

Building your own machines

We’ll wrap this session up by looking at custom functions and modules. So far, we’ve only used built-in functions or those from other people’s modules. But we can make our own!

We’ve only ever called functions - this is what we do when we use them. All functions need a definition, this is the code that gets run when they’re called.

The function definition

Functions are machines. They take some inputs, run some code with those inputs, and spit out one output. We need to define how they work before we use them. We should specify

  • A name
  • Some inputs
  • The code to run (the machine itself)
  • An output

We include these in three steps

  1. The first line of the function definition (the function signature) specifies the name and inputs
  2. We then indent all the code we want to run with our inputs
  3. We end with a return statement, specifying the output
def insert_function_name_here(input_1_name, input_2_name, ...):
    # Code code code
    return output

For example, let’s create a function that converts centimetres to metres.

def cm_to_m(value_in_cm):
    value_in_m = value_in_cm / 100
    return value_in_m

Taking it apart, we have

  • Name: cm_to_m
  • Inputs (just one): value_in_cm
  • Code (just one line): value_in_m = value_in_cm / 100
  • Output: value_in_m

Importantly, nothing happens when you run this code. Why? Because you’ve only defined the function, you haven’t used it yet.

To use this function, we need to call it. Let’s convert \(10\text{ cm}\) to \(\text{m}\).

def cm_to_m(value_in_cm):
    value_in_m = value_in_cm / 100
    return value_in_m

cm_to_m(10)
0.1

When we call the function, it runs with value_in_cm = 10.

That’s it! Every function that you use, built-in or imported, looks like this.

Because functions must be defined before called, and defining them produces no output, best practice is to place functions at the top of your script, below the import statements.

Docstrings

Something you’ll spot on all professional functions are docstrings. This is what Python spits out with the help() function. You can make your own by writing it within triple quotes immediately after the signature ''' ''':

def cm_to_m(value_in_cm):
    """Converts centimetres to metres"""
    value_in_m = value_in_cm / 100
    return value_in_m

cm_to_m(10)
0.1

That said, the best way to ensure clarity is to use a clear name.

Creating modules

What if you need to write lots of functions? We could write unit converters like above for hundreds of possibilities.

It’s useful to tuck these away in their own file, so they don’t clog up your main.py.

Let’s make a new file called conversions.py, and move your function into it. Delete it from your current file.

Then, to make sure it works, let’s make a new converter too, from centimetres to inches. Your conversions.py file should look like:

def cm_to_m(value_in_cm):
    """Converts centimetres to metres"""
    value_in_m = value_in_cm / 100
    return value_in_m

def cm_to_in(value_in_cm):
    """Converts centimetres to inches"""
    value_in_inches = value_in_cm / 2.54
    return value_in_inches

Let’s make another script now called main.py. We’ll run our conversions here by pulling in the functions from conversions.py.

Inside main.py, you’ll need to import conversions.py. To access the functions, you’ll need to use . to look inside the module as usual:

import conversions
metres = conversions.cm_to_m(10)
inches = conversions.cm_to_in(10)

print("10cm is " + metres + "m")
print("10cm is " + inches + "\"")

To include a double quote ” inside a string made of double quotes, escape it with a backslash: \".

Congratulations, you’ve made your first module!