# 2. Exercises: Unconditional String of Exceptions¶

In this exercise we can finally make programs that are a bit more than simple calculation automatons. Execution can differ more drastically between runs of the same program because now the user has more options for interacting with the program. Compared to the last exercises these ones should be quite a bit more challenging when more different situations are entered into the equation. The last set of exercises also give your very first introduction to sub problems of the final project topics. They can be used as a very rough approximation what kinds of things will be encountered in the project.

## Learning Goals¶

After these exercises you can make programs that branch based on the user's choices. You can also inform users about invalid

inputs

and your programs can produce pleasant output. The exercises focus around three key concepts from the material: strings

, exception

handling, and conditional structures

. Dictionaries

are also used. Each task also has some program design to it - it's hard to avoid doing that after all.## Example¶

### Problem Definition¶

Make a program that prompts for two integers, and figures out whether one of them is the other one's factor. Users are notified about invalid inputs. Below are some examples of how the program should look like to the user:

{{{ Input first number: 5 Input second number: 10 5 is a factor of 10.

Input first number: 9 Input second number: -3 -3 is a factor of 9.

Input first number: 7 Input second number: 4 Neither of the numbers is the other's factor.

Input first number: 2 Input second number: -2 2 is a factor of -2.

Input first number: 0 Input second number: 3 3 is a factor of 0.

Input first number: 0 Input second number: 0 0 is a factor of 0.

Input first number: aasi This doesn't look like a number

Input first number: 0 Input second number: aasi This doesn't look like a number

### Solution¶

Let's start with the easy part: prompting the numbers. We should already know how to do this from the last exercises:

```
number_1 = int(input("Input first number: "))
number_2 = int(input("Input second number: "))
```

The testing can be done with modulo. If the remainder is zero, the divider is the dividee's factor.

Let's think about how this program would branch. There are two possible prints: either one of the numbers is the other one's factor, or neither is. This could hint at there being two

branches

like seen below. At this stage we'll just use pass inside the branch for each conditional statement

. ```
if number_1 % number_2 == 0 or number_2 % number_1 == 0:
pass
else:
pass
```

This results in an immediate problem: which one was the factor of the other? The conditional statement we devised doesn't give us that information. We'd need another test inside this branch to figure out which was which. We could do that by checking which number is smaller since a bigger number can't be the factor of a smaller number. We're not doing that though because it makes things unnecessarily complicated. Instead we split this branch into two branches:

```
if number_1 % number_2 == 0:
pass
elif number_2 % number_1 == 0:
pass
else:
pass
```

With this we know that when in the first branch,

*number_2*was a factor of*number_1*, and likewise in the second branch we know it's the other way around. We can now replace the placeholder pass commands with prints. We do this by copying the text from the given examples and replacing numbers withplaceholders

. Notice the changing order of arguments

between branches.```
if number_1 % number_2 == 0:
print(f"{number_2} is a factor of {number_1}.")
elif number_2 % number_1 == 0:
print(f"{number_1} is a factor of {number_2}.")
else:
print("Neither of the numbers is the other's factor.")
```

Let's combine this with the input prompts we wrote earlier and try it out:

Input first number: 4 Input second number: 0 --------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) /media/sf_virtualshare/OA/code/H2/factor.py in1 number_1 = int(input("Input first number: ")) 2 number_2 = int(input("Input second number: ")) ----> 3 if number_1 % number_2 == 0: 4 print(f"{number_2} is a factor of {number_1}.") 5 elif number_2 % number_1 == 0: ZeroDivisionError: integer division or modulo by zero

Oh yeah, zero might be a problem since we're doing division with the numbers. We know that all numbers are factors of zero and zero is a factor of itself. In other words, if one of the numbers is zero, the other number will always be its factor. This time we're wiser and don't even try to put this inside one

branch

. Instead, we'll add two. This time it is also extremely important to consider the placement of these new branches. If the user inputs

zero, code execution cannot be allowed to reach any conditional statements

that contain division. This can be achieved by putting the new branches before the old ones. This works because only the first branch with a fulfilled condition

is executed - everything after it in the same conditional structure

is skipped. With this addition we have:```
number_1 = int(input("Input first number: "))
number_2 = int(input("Input second number: "))
if number_1 == 0:
print(f"{number_2} is a factor of {number_1}.")
elif number_2 == 0:
print(f"{number_1} is a factor of {number_2}.")
elif number_1 % number_2 == 0:
print(f"{number_2} is a factor of {number_1}.")
elif number_2 % number_1 == 0:
print(f"{number_1} is a factor of {number_2}.")
else:
print("Neither of the numbers is the other's factor.")
```

Let's try again:

Input first number: 4 Input second number: 0 4 is a factor of 0.

We're now golden. At least as long as the user actually

inputs

integers. We still need to handle invalid inputs. This can be done with a try-except structure that catches the ValueError exception

- like we did in the material.```
try:
number_1 = int(input("Input first number: "))
number_2 = int(input("Input second number: "))
except ValueError:
print("This doesn't look like a number")
```

This is fine, sort of, except if we actually put it as-is in the beginning of the program, we achieve new problems:

Input first number: donkey This doesn't look like a number --------------------------------------------------------------------------- NameError Traceback (most recent call last) /media/sf_virtualshare/OA/code/H2/factor.py in4 except ValueError: 5 print("This doesn't look like a number") ----> 6 if number_1 == 0: 7 print(f"{number_2} is a factor of {number_1}.") 8 elif number_2 == 0: NameError: name 'number_1' is not defined

If you look carefully you can see that the notification was printed like we wanted. Unfortunately the program still crashed. The culprit is the NameError caused by line 6 in the code. We really got our work cut out for us... The crux of the problem is that the execution of line 2 is interrupted immediately when the int function fails. This means that

*number_1*was never defined. Obviously there's little point in going through the conditional structure if we don't have two numbers to compare. The correct behavior would be to skip rest of the program if there is an exception in the input part. We can achieve this by adding an else branch to our try structure and pushing the rest of the code inside it:```
try:
number_1 = int(input("Input first number: "))
number_2 = int(input("Input second number: "))
except ValueError:
print("This doesn't look like a number")
else:
if number_1 == 0:
print(f"{number_2} is a factor of {number_1}.")
elif number_2 == 0:
print(f"{number_1} is a factor of {number_2}.")
elif number_1 % number_2 == 0:
print(f"{number_2} is a factor of {number_1}.")
elif number_2 % number_1 == 0:
print(f"{number_1} is a factor of {number_2}.")
else:
print("Neither of the numbers is the other's factor.")
```

Understanding the above construct is of extreme importance for completing exercises in this page. Study it to your heart's content. Now the program works:

Input first number: donkey This doesn't look like a number

## Closing Programs¶

This exercise contains tasks that will ask you to stop the program execution when an error is detected. This means that notifying the user about their error should be the last executable line in that particular branch in the program. The structure of the program has to be designed in a way that every error notification happens inside a control structure in such a way that there will be nothing left to execute after it. If you try to google how to exit a Python program, you can easily find answers using the

*quit*and*exit*functions. However, these cannot be used for two reasons.The first reason is related to learning: one major goal of this exercise is to learn to control the flow of the program with control structures - without shortcuts. Shortcuts can be used when the fundamentals of program flow control have been learned. The other reason is technical: these functions will shut down the entire Python process. The checkers work by importing the returned program as part of themselves to run it and call its functions. Therefore, these forbidden functions will not only close the returned program, they will also close the checker. As a result you will get the dreaded "The checking program was unable to finish" error.

## General Exercises¶

All these exercises are mandatory parts of the course.

### Warmup Exercise¶

### Main Exercises¶

## Course Project Exercises¶

You only need to do one of these exercises. Each of them is related to one of the course project topics. Choose the one that is in line with your chosen topic. These tasks are not required for minimal course completion.

# Give feedback on this content

Comments about these exercises