Closure#

Syllabus week 4: Loops#

  • Looping over a sequence (known number of iterations), keyword for and keyword in, the range() function

  • Understanding that indexing variable in the for loop is reassigned with each iteration

  • Looping as long as a condition is true (unknown number of iterations), keyword while

  • Understanding that the variables used in the condition need to be defined before the while loop

  • Understanding that the variables used in the condition should be changed in the while loop body for loop to terminate

  • Keyword break

  • Solve simple problems that require a lot of computation e.g. simulations and population models

  • The concept of pseudocode

Advanced#

Advanced 4.1: Keywords pass and continue#

The pass keyword is does nothing! It can be useful as a placeholder when you are writing code and you don’t want to do anything yet but you need to have an indented block. For example:

if condition:
    #bla

will give an error, since the code block is empty (comment doesn’t count). Instead, you can use pass:

if condition:
    pass

The continue keyword is used to skip the rest of the code block and continue with the next iteration of the loop. For example, for code that prints all numbers from 0 to 9, but skips the number 5:

for i in range(10):
    if i == 5:
        continue
    print(i)

It is easy to avoid using pass or continue, but sometimes they can make the code more readable.

Advanced 4.2: Walrus operator for pi approximation#

The walrus operator := is used to assign a variable in the middle of expressions. For example, you can assign variables from the condition of an if statement. This can be useful when you want to use the condition (or its parts) later in the code. For example:

name = "Bartholomew"
if name_is_long := len(name) > 10:
    print(f"Wow, {name} is a long name!")
print(f"Value of name is long: {name_is_long}")

In fact, you can use it multiple times, for example:

name = "Christabelle"
if name_is_long := (length_of_name := len(name)) > 10:
    print(f"Wow, {name} is a long name!")
print("Value of name is long:", name_is_long)
print("The length of the name is:", length_of_name)

In the code above, the parentheses are important. Try removing them and see what happens.

Now you will modify some code, by adding the walrus operator and reducing the number of lines of code. The code should still produce the same output. That is, it should approximates \(\pi\) by using the formula

\[ \pi = 4 \cdot \left(1 - \frac{1}{3} + \frac{1}{5} - \frac{1}{7} + \frac{1}{9} - \frac{1}{11} + \dots \right).\]
pi = 3.141592653589793
error_threshold = 0.01
pi_approx = 0
max_divisor = 100 # try to change to e.g. 300 and see if it converges
sign = 1
converged = False # walrus
for divisor in range(1, max_divisor+1, 2):
    pi_approx += sign * 4 / divisor
    sign = sign * -1
    error = abs(pi - pi_approx) # walrus
    if error < error_threshold:
        converged = True # walrus 
        break
print("The approximation of pi is", pi_approx)
print("The error is", error)
if converged:
    print("The error is small enough")
else:
    print("The error is too large")

The lines in the code marked with # walrus can all be removed and assignments can be done from the conditions in the if statements. Try achieving this.

If you want to know why the operator is called the walrus operator, the image below might give you a hint.

image

Advanced 4.3: Using _ as variable name#

Check this code.

for _ in range(3):
    print("Hello!")

In Python, is common to use _ as the name for the variable which will not be used later. For example, if the iteration variable is not used in the loop. There is nothing special about the _ variable name in Python, and using it is just a convention.

Advanced 4.4: Why does it stop?#

You know from mathematics that if you halve a positive number, the result will still be positive. So repeated halving should approach, but never reach zero. Try now to run the following code, and give it some time to finish executing.

x = 5
while x>0:
    print(x)
    x = x/2

The code execution stops because at some point x becomes so small that it cannot be represented accurately as a floating-point number. It gets so small that is rounded to zero. Confirm this by printing following values. This is something to keep in mind when working with very small or very large floating-point numbers.

Run the code below to see another example of this behavior when comparing two numbers that should be the same. It is therefore a good idea to be careful when comparing floating-point numbers for equality, and instead check if the difference between them is smaller than some threshold.

a = 1 ** 50 / 200 ** 50
b = (1 / 200) ** 50
print(a)
print(b)
print(a == b)  # False due to floating point error
print(abs(a - b) < 1e-10)  # A better way to compare floating point numbers

Advanced 4.5: Efficient looping#

It is a good coding practice to calculate as much as possible outside the loop. This is especially important if the loop is executed many times. Consider the following code which computes the volume of water in a cylinder for different heights. Can you improve it?

import math
for height in range(1, 6):
    pi = math.pi
    radius = 0.8
    volume = pi * (radius ** 2) * height
    print("Volume is", volume)

Assigning constants, pi and radius, inside the loop is not needed because they are the same for all iterations. Furthermore, a part of the calculation can be moved outside the loop, by calculating V1, the volume of the cylinder with height 1, before the loop.