Class 11: Conditionals and While Loops

Objectives for today

Non-boolean types that can be used as booleans

Will the following print statement get executed?

if "a string?":
	print("Do I get executed?")

Yes. Most values can be used in a boolean context. In Python, 0, 0.0, None, empty sequences (e.g. “”) and a few other values evaluate as False and everything else is True.

In general, I don’t recommend using this “implicit” type conversion as it just increases the chances for difficult-to-find bugs.

Now that we know about implicit booleans, though, we can resolve a common bug. In a previous question, the solution was a == b or a == 5. Can we simplify that expression as a == b or 5? No, that expression is evaluated as a (a == b) or 5, which is not the same (and will always evaluate to True as 5 evaluates as True).

Short circuit evaluation

Let’s think about a and b. If a evaluates to False do I need to evaluate b? Similarly if in a or b a evaluates to True do I need to evaluate b?

No. Python can “short-circuit” the evaluation. Doing so is a very powerful technique for managing potentially problematic situations.

is_valid(input) and dangerous_operation(input)

Comparing string and other non-numeric types

We can also apply relational operators, i.e. <, ==, etc., to other types; most notably strings. For example:

>>> 'Aardvark' < 'Zebra'
True
>>> 'aardvark' < 'Zebra'
False

That doesn’t seem to make much sense… Just as + has different meaning for strings than integers, the < for strings implements lexicographic ordering, i.e. it compares the ordering of corresponding characters. The first characters are compared, if equal, then the 2nd characters are compared and so on. If one string is a substring of the other, it is lexicographically less than, e.g.

>>> "abc" < "abcdef"
True

This is not the same as a case-insensitive alphabetical ordering. In the character encoding used by Python (and lots of other software), all upper case letters are less than lower case letters. Hence “aardvark” is “greater than” “Zebra”.

Some more examples:

>>> "test" == "test"
True
>> "test" == "testing"[0:4]
True
>> "test" == "TEST"
False

If we wanted to ensure that a string comparison was case insensitive, how could be we do so? Use the upper or lower methods to ensure consistent case.

while loops

We previously used for loops to execute a block of code for a specific number of repetitions. What if we don’t know how many iterations are needed? What might be such a situation? Obtaining a valid input from a user. We don’t know how many tries it will take someone to provide a valid input.

This is where we apply while loops.

General structure:

while <bool expression>:
	statement1
	statement2
	...
	statementn

The statements in the loop body (i.e. statement1 … statementn) will be executed repeatedly until the boolean expression, the loop conditional, evaluates to False.

Here is a concrete example. What will this code print?

>>> x = 5
>>> while x > 0:
... 	print(x)
... 	x = x-1
... 
5
4
3
2
1

What is the implication of while loops? That some statement(s) within the body of the while loop will change the loop conditional (or otherwise terminate the loop). What happens if that is not the case, e.g.

while True:
    print("How many times will this string get printed?")

Hit Ctrl-C (Ctrl and C simultaneously) or the Thonny “stop sign” button to stop execution.

This is called an infinite loop and is a common problem. You will likely need to use Ctrl-C or the stop sign button at some point.

What about the following loop? Will it terminate?

i = 0
while i < 10:
    print("How many times will this string get printed?")
    i = i - 1

No. Because i starts at 0 and only gets smaller it will always be less than

  1. What about this loop?
i = randint(1, 20)
while i < 10:
    print("How many times will this string get printed?")
    i = i + 1

Yes. If the value is less than 10, the loop with terminate after some number of iterations. If the value is greater than or equal to 10, the loop won’t execute at all.

In addition to changing the loop conditional we can also explicitly terminate the loop with the break statement. As its name suggests, break terminates the loop and begins executing the first statement after the loop.

PI Questions (While loops)1

Random Guessing Game

Let’s look at a more complex example: Let’s implement a guessing game in which Python picks a random integer from 1-20 (inclusive), and keeps asking the user to guess the number until they get it right. To help the user, the game should give hints “higher”, or “lower”.

What would be some key elements in such a program?

Check out guessing-game.py.

Here we see 3 strategies for implementing the loop:

  1. Use a boolean variable correct to track if the user answered correctly
  2. break out of a “while True” loop on a correct answer
  3. Compare the user response to the desired answered in the loop conditional

Note the use of the input function for reading user inputs:

>>> help(input)
Help on built-in function input in module builtins:

input(prompt=None, /)
    Read a string from standard input.  The trailing newline is stripped.
    
    The prompt string, if given, is printed to standard output without a
    trailing newline before reading input.
    
    If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError.
    On *nix systems, readline is used if available.

input returns a string and so we need to convert the result to an int for use in our game.

While vs For loops

Often we can implement the same functionality with both a for loop and a while loop. In fact, in Python any for loop can be readily implemented with a while loop. The reverse is trickier. There are hacks that would enable us to make a for loop behave like a while loop in some situations, but they are exactly that – hacks. So for our purposes we should think of while loops as a superset of for loops.

So when do we use a for loop and when do we use a while loop?

We use a for loop when

As an example, iterating over all the elements in a sequence, e.g. a string, is an example of a situation where the number of iterations is known at the initiation of the loop (number of elements in sequence) and the increment is consistent (increment one element each iteration).

We would use a while loop in other settings, such as

This an example where “style” matters but there are not necessarily clear “rules”. Often one approach or the other is more appropriate. The right choice will make the code more elegant, easier to reason about (and easier to debug).