Functional decomposition, also called functional abstraction, is the breaking down of a problem into smaller parts. Each part becomes a function. Functions should solve a specific problem -- this makes them more versatile. Ideally, you would decide on what functions to create before you write your program. Of course, sometimes it isn't until you're writing your program that you realize a better way to write the program, so sometimes you add functions as you're writing your program.
Good guidelines for deciding when to create functions are:
For the following programming prompt, what functions do you think would be useful?
Non-function program: sales_amounts.py
Sample data: Sales_Amounts.txt
So far this semester, all code we wrote interacted with "the user", the person running our program. Now, "the user" may be another programmer. Just like we have been using code written by others (e.g. print
, int
, range
), other programmers might use code that we write in their own programs. This means that we need to keep two things in mind:
The problems with the example functions above stem from not doing those two things. In the next two sections, we will look at how to address the above two issues.
Sometimes, the programmer calling our function passes in the wrong information. It could be wrong because:
open
function a file that doesn't exist and asking it to open the file for reading)In these cases, you need to alert the user of your function that bad information was given to the function. Since the user of your function may be another programmer and not the person running the program, you cannot just print an error to the screen. For example, this is the wrong way to alert the "user" that the wrong value was given:
It's primarily wrong because you're alerting the wrong person that num
was wrong. You're alerting the person running the program, but in most cases the person running the program isn't capable of fixing the problem. A secondary reason this is wrong is because the function returns None
on bad input and a number on good input. While this might not be a bad thing, it's possible for a sloppy programmer to not check whether factorial
returned None
.
The prefered way to deal with bad input to a function is with exceptions. You can raise an exception on bad input to a function by using the raise
statement. The raise statement raises an exception. We've already seen how to catch these exceptions, now we're seeing how to raise (also called "throw") them.
Now, if a negative value is passed into factorial
, an exception is raised instead of None
being returned. When an exception is raised, the function stops where it is and Python starts looking for the exception handling environment to catch this exception. The exception will propagate back up to the calling function and if there is no exception handling environment to catch it, it'll go to that function's calling function. This will continue until either an exception handling environment is found, or it reaches the top and the program crashes. Here's an example:
Code | Output |
---|---|
if num < 0: raise ValueError('The number must be positive') product = 1 for i in range(1, num+1): product *= i return product def user_input_factorial(): val = int(input("Enter a positive integer: ")) try: print("That number's factorial is:", factorial(val)) except ValueError as ve: print(ve) user_input_factorial() user_input_factorial() |
That number's factorial is: 120 Enter a positive integer: The number must be positive |
Code | Output |
---|---|
if num < 0: raise ValueError('The number must be positive') product = 1 for i in range(1, num+1): product *= i return product def get_user_factorial(): val = int(input("Enter a positive integer: ")) fact = factorial(val) print('Got the factorial') return fact def print_user_factorial(): user_fact = get_user_factorial() try print("The factorial of that number is:", user_fact) except ValueError: print('Error with computing factorial') print_user_factorial() print_user_factorial() |
Got the factorial The factorial of that number is: 120 Enter a positive integer: Error with computing factorial |
What's wrong with returning None
on bad input? Why bother with exception handling environments? Exceptions allow us to do more than just indicate whether the function worked correctly or not. Our factorial function actually has two different kinds of bad arguments. The first is negative numbers, which are handled already. The second is that it only works on integers. If the programmer using our function tries to pass in a string or a float, the function will fail. To fix our problem, we need to do another kind of check at the start of the function. We need to check whether the function was given an integer. To do that, we can use the isinstance
function. It takes a value and a data type and returns True if the value is an instance of that data type. For example:
You can pass in a list/tuple of data types if you'd like. Maybe you're just curious if it's a number or not. You can easily do that with:
In a function, we can use it to check whether the correct data type was given. For our factorial function, it only works on positive integers, so we must check whether an integer is given (we're already checking whether it's positive:
Does it matter which check comes first? Can we check whether it's positive before we check whether it's an integer?
How do you know which kind of error to raise? Python provides a lot (and we can even create our own). For now, we'll primarily be using these errors:
The complete list of built-in exceptions can be found in Python's documentation.
<< Previous Notes | Daily Schedule | Next Notes >> |