Control/Flow statements

At the moment we've only seen how to store and manipulate different types of variable. Now we'll see how to loop and use if-else statements in Python.

If-Else


In [1]:
a = 1
if a == 1:
    print("a = 1")
    b = a + 2  # Indentation level indicates scope of 'if'
elif a == 2:  # Optionally can have 'elif' or 'else' statements 
    print("a = 2")
else:  # 
    print("a could be almost anything")
print(b)


a = 1
3
  • Above, a was defined outside of the if-else block. But b was defined inside the indented block and is still accessible outside the block.
  • If-else blocks don't have their own variable scope.
  • Boolean comparisons are supported and can be chained together. It's best to use parentheses to explicitly define the order they will evaluate in.
  • They use English language and, or, not rather than the boolean operators &&, ||, !
  • You can also use the is keyword as before, or a new one in. This lets you check if an element is in an iterable object like a list.

In [2]:
a = True
b = False
if a or b:
    print("a or b")
elif a and b:
    print("a and b")
elif a and not b:
    print("a and not b")
else:  # No
    print("Failed all somehow")


a or b

In [3]:
l = ["a", "b", "c"]
if "d" not in l:  # Don't have to loop and check each elemnt for elem == 'd'
    print("Hooray!")


Hooray!
  • By the way, most objects in Python have a boolean value when used with if.

In [4]:
a = ""
if a:
    print("Variables can be true")
else:
    print("Or false")


Or false

Looping

  • In general when you loop, you are probably looping over a set of values, using each value in turn for some operation.
  • In C you will often need to know the number of values contained in an array and then loop over indices from 0->n-1 and pick out the value each element of the array.
  • In Python you rarely use indices to loop, but instead loop directly over an iterable object e.g. a list. This means that you don't need to know the size of the container or the objects inside as the loop will end when the elements run out.

For loops

  • Default for loops start at the 0th index and progress forwards until they reach the last element, then they end.

In [5]:
a = ["a", "c", "e", "d", "b"]
for elem in a:    # Simple for loop, variable 'elem' is the element currently reached
    print(elem, end=" ")  # I'm changeing the newline at the end of the print for a space


a c e d b 

In [6]:
for elem in reversed(a):   # Can loop in reverse order
    print(elem, end=" ")


b d e c a 

In [7]:
for i,elem in enumerate(a):   # Use enumerate() if you want the index and the element
    print(i, elem)


0 a
1 c
2 e
3 d
4 b
  • The syntax for enumerate above is using tuple (Un)packing.
  • You might wonder how Python knows what to assign to i and elem. The enumerate function and for loop are actually returning tuples with the 0th element being the index of the current element, and the second being the element itself.
  • The syntax i,elem unpacks the values inside this implicit tuple into the two variables during each iteration.
  • This shorthand syntax eliminates a lot of boilerplate code for a common use for looping.

In [8]:
for elem in sorted(a):  # Can sort in an obvious way, then iterate
    print(elem, end=" ")

print()
print(a)   # Note that the sorted() function made a copy and didn't affect the list.
a.sort()   # list.sort() DOES re-order the elements in place.
print(a)


a b c d e 
['a', 'c', 'e', 'd', 'b']
['a', 'b', 'c', 'd', 'e']
  • The sorted() function is much more flexible though. It can take key functions as an argument to give you a custom sort order.
  • Much better performance than comparison functions.
  • Simply pass in a function that takes an element and returns a value that can be used for sorting.

In [9]:
a = ["Dave", "Andy", "Charlotte", "Emma", "Darcey", "Rob"]
print(len(a))   # The built in len() function takes an iterable and gives you its length
for elem in sorted(a, key=len):   # Will iterate by length of string
    print(elem)


6
Rob
Dave
Andy
Emma
Darcey
Charlotte
  • What about looping over two or more lists simultaneously, use the zip() function.

In [10]:
a = ["Dave", "Andy", "Charlotte", "Emma", "Darcey", "Rob"]
b = ["Handsome", "Uggo", "Busy"]
# Note the two lists are different lengths
for name,adjective in zip(a,b):  # Zip is fast and memory efficient
    print(adjective,name)
print(list(zip(a,b)))  # Zip dosn't generate a container until you want one


Handsome Dave
Uggo Andy
Busy Charlotte
[('Dave', 'Handsome'), ('Andy', 'Uggo'), ('Charlotte', 'Busy')]
  • What if you want to loop over a simple sequence of numbers that you haven't created yet. Use the range() function.
  • The range() function takes the arguments range( start, stop, step ) where start and step are optional. You can give only the stopping value and you get the integers from 0->(stop-1).

In [11]:
for i in range(10):   # The range() function gives you iterators for a sequence you define
    print(i, end=" ")


0 1 2 3 4 5 6 7 8 9 

In [12]:
for i in range(9, -1, -1):  # Start at 9, decrease by 1 and stop looping when hitting -1.
    print(i, end=" ")

print()
for i in range(0, 20, 3):   # Start at 0, increase by 3 and stop looping when hitting 20.
    print(i, end=" ")


9 8 7 6 5 4 3 2 1 0 
0 3 6 9 12 15 18 

In [13]:
l = list(range(10))  # Can use range to rapidly make lists/tuples/etc of sequences.
print(l)


[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  • Note that we had to call list(range(10)) above to make the list, because although the object returned by range() behaves like a list in a loop, it isn't one in Python 3!
  • To save processing time and memory, range() and functions like it return each value sequentially without making the full list object until you explicitly make it with the list() function. This means you can safely loop over use enormous ranges/zips without running out of memory.

Dictionaries

  • Dictionaries contain a key/value per element, how do you loop over them? Remember the view objects.

In [14]:
d = {"Dave":27, "Andy":28, "Emma":4, "Darcey":2}
for k in d:  # Looping directly over the dictionary gives you the keys
    print(k)


Emma
Andy
Darcey
Dave

In [15]:
# Need to use methods of dictionary to get at values or key-value pairs
items = d.items()
keys = d.keys()
values = d.values()
for it,k,v in zip(items, keys, values):  # Don't assume these objects have the same order!
    print(k,v,it)


Emma 4 ('Emma', 4)
Andy 28 ('Andy', 28)
Darcey 2 ('Darcey', 2)
Dave 27 ('Dave', 27)

List comprehensions

  • Remember that we can initialise a list by putting the elements inside square brackets [].
  • Since for loops return elements for use in each iteration you can imagine generating a new list by inserting the for loop inside the brackets.

In [16]:
l = list(range(10))  # Make a quick list
l_sq = [elem**2 for elem in l]  # Square the elements and put the result into the new list
print(l)
print(l_sq)


[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
  • A list comprehension consists of an expression, followed by a for loop. After that you can add many more for loops or even if statements, in the same order as if they were ordinary loops.
  • However don't get cocky as they can quickly become unreadable if overused.

In [17]:
l_odd =[ elem for elem in l if (elem%2) != 0 ]  # Only add odd numbers
print(l_odd)


[1, 3, 5, 7, 9]

In [18]:
vec = [(1,2,3), (4,5,6), (7,8,9)]  # Let's flatten this
l_flat = [ num for elem in vec for num in elem ]  # Nested loops
print(l_flat)


[1, 2, 3, 4, 5, 6, 7, 8, 9]

Extra clauses in 'for' loops

  • There are additional ways to control how 'for' loops progress, using the break, continue, pass and else statements.

In [19]:
for i in range(10):
    pass   # Used when there's nothing else to be put here

In [20]:
for i in range(10):
    print(i, end=" ")
    if i==5:
        break   # Exits the loop at this point


0 1 2 3 4 5 

In [21]:
for i in range(10):
    if (i%2)==0:  # Check for even numbers
        continue  # Progresses to next iteration missing rest of loop block
    print("An odd number",i)


An odd number 1
An odd number 3
An odd number 5
An odd number 7
An odd number 9
  • There are technically two outcomes of a for loop. Either the loop ends normally or it hits a break statement that ends it early.
  • In the olden days of goto statements people knew that for loops had an implicit if and goto embedded in them i.e. If I haven't reached the end condition of the loop goto the beginning and loop again.
  • Based on this idea, for loops in Python can use an else statement after them which is only run if the for loop finished without a break statement.
  • This can be very helpful to use instead of keeping track of flag variables.

In [22]:
for i in range(10):
    print(i, end=" ")
    if i==15:
        break
else:
    print("\nMade it to the end!")
    # Note that the 'else' is connected to the 'for' not the 'if'


0 1 2 3 4 5 6 7 8 9 
Made it to the end!

While loops

  • While loops are much the same as they always are.

In [23]:
a = 0
while a < 10:
    print(a, end=" ")
    a+=1


0 1 2 3 4 5 6 7 8 9 
  • Just as with for loops you can use pass, break, continue and even the else clause with while loops i.e. If the loop exits without using a break statement the else code will be run.

In [24]:
a = 0
while a < 10:
    print(a, end=" ")
    if a == -1:
        break
    a+=1
else:
    print("\nCan't break this!")


0 1 2 3 4 5 6 7 8 9 
Can't break this!