Control Flow

  • Without Control flow, a program is simply a list of statements that are sequentially executed.
  • With control flow, you can execute certain code blocks conditionally and/or repeatedly.
  • Basic building blocks are:
    • conditional statements (including "if", "elif", and "else")
    • loop statements (including "for" and "while" and the accompanying "break", "continue", and "pass").

Conditional Statements: if-elif-else:

Conditional statements, often referred to as if-then statements, allow the programmer to execute certain pieces of code depending on some condition.

A basic example of a Python conditional statement is this (we need to use a colon to specify an 'if, elif or else' statement:


In [1]:
foo = 'bar'

# The 'if' statment will set a criteria, and 'if' the condition is met, it will print 'Condition satisfied'
if foo == 'bar':
    print('Condition satisfied')


Condition satisfied

In [2]:
x = -15

# If x is equal to zero print x, "is zero"
if x == 0:
    print(x, "is zero")
    
# Other wise if x is greater than zero print x, "is positive"
elif x > 0:
    print(x, "is positive")
    
# If all else fails print x, "is negative"
else:
    print(x, "is negative")


-15 is negative

Note especially the use of colons (:) and whitespace to denote separate blocks of code.

for loops

Loops in Python are a way to repeatedly execute some code statement.

This saves time rather than writing out each line, but can also be computationally demanding!

So, for example, if we'd like to print each of the items in a list, we can use a for loop:


In [3]:
for N in [2, 3, 5, 7]: 
    print(N)


2
3
5
7

Now lets print it on the same line for ease:

A 'for loop' is a basic and extremely helpful tool in python.

What we are doing here is replace the 'N' with each number within the list ie. [2,3,5,7] sequentionally, and printing the output.

For each number you printed, you can see it has been placed on a new line. This signifies the loop has restarted on the next value in the list.

Now let's do the same but this time put all the numbers on the same line for visual appearance.


In [4]:
for N in [2, 3, 5, 7]: # you can replace 'N' with anything you like, but make sure to replace it below too!
    print(N, end=' ') # print all on same line


2 3 5 7 

range(start, stop[, step])

One of the most commonly-used iterators in Python is the range object, which generates a sequence of numbers:


In [5]:
for i in range(10):   # Here we have replaced 'N' with 'i' 
    print(i)          #print(i, end=' ')


0
1
2
3
4
5
6
7
8
9

Range objects can also have more complicated values:


In [6]:
# range from 5 to 10
list(range(5, 10))


Out[6]:
[5, 6, 7, 8, 9]

In [7]:
# range from 0 to 10 by 2
list(range(0, 10, 2))


Out[7]:
[0, 2, 4, 6, 8]

while loops

The other type of loop in Python is a while loop, which iterates until some condition is met.

It can also be used as a continous loop until you 'break' it!


In [8]:
i = 0
while i < 10:
    print(i, end=' ')
    i += 1


0 1 2 3 4 5 6 7 8 9 

The argument of the while loop is evaluated as a boolean statement, and the loop is executed until the statement evaluates to False.

break and continue: Fine-tuning your For and While loops

  • break statement breaks-out of the loop entirely
  • continue statement skips the remainder of the current loop, and goes to the next iteration

Let's print a string of odd numbers.


In [9]:
for n in range(20):
    # check if n is even
    if n % 2 == 0:
        continue
    print(n,end=" ")


1 3 5 7 9 11 13 15 17 19 

In this case, the result could be accomplished just as well with an if-else statement, but sometimes the continue statement can be a more convenient way to express the idea you have in mind:

Here is an example of a break statement used for a less trivial task. This loop will fill a list with all Fibonacci numbers up to a certain value:


In [10]:
a, b = 0, 1
amax = 100
L = []

while True:
    (a, b) = (b, a + b) 
    if a > amax:
        break
    L.append(a)

print(L)


[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

Notice that we use a while True loop, which will loop forever unless we have a break statement! Above we set a value of 100 to break if the value 'L' becomes greater than 100.

Exercise 1

Write a program that can print the following pattern for a given triangle size N.

For N=5: * * * * * * * * * * * * * * *

print('*',end=' ') # to print '*' without changing line print('') # change line </code>

Iterators

range(): A List Is Not Always a List

The range() function in Python 3 (named xrange() in Python 2), returns not a list, but a special range() object:


In [11]:
import sys
if sys.version_info < (3,0):
    range = xrange

range, like a list, exposes an iterator:


In [12]:
iter(range(10))


Out[12]:
<range_iterator at 0x7fe64407afc0>

So Python knows to treat it as if it's a list:


In [13]:
for i in range(2):
    print(i)


0
1

In this indirect iterator the full list is never explicitly created!

Let's try something sily:


In [14]:
N = 10 ** 9             
for i in range(N):            # this would take > 4 GBytes to store in memory
    if i >= 10: break
    print(i)                   # print(i, end=', ') in python 3

sys.getsizeof(range(N))


0
1
2
3
4
5
6
7
8
9
Out[14]:
48

If range were to actually create that list of one trillion values, it would occupy tens of terabytes of machine memory: a waste, given the fact that we're ignoring all but the first 10 values!

In fact, there's no reason that iterators ever have to end at all! Python's itertools library contains a count function that acts as an infinite range:


In [15]:
from itertools import count

for i in count():
    if i >= 10:
        break
    print(i, end=', ')


0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 

Had we not thrown-in a loop break here, it would go on happily counting until the process is manually interrupted or killed (using, for example, ctrl-C).

If range were to actually create that list of one trillion values, it would occupy tens of GB of machine memory: a waste, given the fact that we're ignoring all but the first 10 values!

Useful Iterators

enumerate

Often you need to iterate not only the values in an array, but also keep track of the index. You might be tempted to do things this way:


In [16]:
L = [2, 4, 6, 8, 10]
for i in range(len(L)):
    print(i, L[i])


0 2
1 4
2 6
3 8
4 10

Although this does work, Python provides a cleaner syntax using the enumerate iterator:


In [17]:
for i, val in enumerate(L):
    print(i, val)


0 2
1 4
2 6
3 8
4 10

zip([iterable, ...])

Iterate through several iterables at the same time.

In [18]:
L = [3, 3, 6, 8, 15]
M = [2, 6, 8, 10, 12]
R = [1, 4, 5, 9, 10]
for lval, mval, rval in zip(L, M, R):
    print(max(lval, mval, rval))
    
#Alternative to
for i in range(len(L)):
    max(L[i], M[i], R[i])


3
6
8
10
15

Any number of iterables can be zipped together, and if they are different lengths, the shortest will determine the length of the zip.

map(function, iterable, ...)

Apply function to every item of iterable and return a list of the results.

In [19]:
list(map(max, L, M, R))


Out[19]:
[3, 6, 8, 10, 15]

itertools

More specialised iterators are available in a built-in itertools module. Example:


In [20]:
from itertools import permutations
p = permutations(range(3))
print(*p)


(0, 1, 2) (0, 2, 1) (1, 0, 2) (1, 2, 0) (2, 0, 1) (2, 1, 0)

Errors and exceptions

These things happen... Mistakes come in three basic flavors:

  • Syntax errors: Errors where the code is not valid Python (generally easy to fix)
  • Runtime errors: Errors where syntactically valid code fails to execute, perhaps due to invalid user input (sometimes easy to fix)
  • Semantic errors: Errors in logic: code executes without a problem, but the result is not what you expect (often very difficult to track-down and fix)

Here we're going to focus on how to deal cleanly with runtime errors.

Catch what you can handle vs. let it fail

Errors that go unoticed can be difficult to detect, so often the best option is to let the exception stop the program.

try and except

Sometimes specific errors can be handled and an alternate course of action taken.


In [21]:
import sys
### Handling exceptions
try:
    open('myfile')
except IOError:
    err = sys.exc_info()[1]
    print(err)
    print('Cannot read file so will use  default values')
    # Do alternate action
    
else:
    print('Values were read from myfile')


[Errno 2] No such file or directory: 'myfile'
Cannot read file so will use  default values

References

A Whirlwind Tour of Python by Jake VanderPlas (O’Reilly). Copyright 2016 O’Reilly Media, Inc., 978-1-491-96465-1

Solutions to Exercise 1


In [22]:
N=5
#Solution
for i in range(N):
    for j in range(i+1):
        print ('*', end=' ')
    print('')
print('')


* 
* * 
* * * 
* * * * 
* * * * * 


In [23]:
#Solution using break
for i in range(N):
    for j in range(N):
        if (j > i):
            break
        print('*', end=' ')
    print('')


* 
* * 
* * * 
* * * * 
* * * * *