Here's a brief example of how to use exceptions in Python:
```python
try:
# Attempt to perform some operation that might raise an exception
result=10/0# This will raise a ZeroDivisionError
exceptZeroDivisionError:
# Handle the specific exception that was raised
print("Error: Division by zero is not allowed.")
exceptExceptionase:
# Handle any other exceptions that were not caught by the previous except block
print("An error occurred:",str(e))
else:
# Code to be executed if no exceptions were raised
print("The result is:",result)
finally:
# Code that will always be executed, regardless of whether an exception was raised or not
print("Program execution has finished.")
```
In this example, we attempt to divide the number 10 by 0, which is not allowed and will raise a `ZeroDivisionError`. We then use a `try-except` block to catch and handle this specific exception. If any other exception is raised, the program will catch it in the `except Exception as e` block. The `else` block is executed if no exceptions were raised, and the `finally` block is always executed, regardless of whether an exception was raised or not.
To explore python exceptions in more depth here are some external resources. In particular, you might consider a component of this course from LinkedIn courses (which you can access using your UWE account):
[Linked in Learning - Python Exceptions](https://www.linkedin.com/learning/python-essential-training-18764650/errors-and-exceptions?resume=false&u=56744785)
Here's an example of a function that divides a number by another number, using three arguments to manage the 'data' and a return value for error handling:
We can see this has some limitations in 'ergonomics' as we can no longer compose functions as they return errors.
```python
defdivide_numbers(dividend,divisor,result):
ifdivisor==0:
returnFalse
else:
result[0]=dividend/divisor
returnTrue
```
In this function, we check if the divisor is zero. If it is, we return `False` to indicate that the division cannot be performed. Otherwise, we calculate the result by dividing the dividend by the divisor and modify the `result` argument using reference.
Here's examples of how you can use this function:
### Initialise the variables
```python
dividend=10
divisor=2
result=[0]# Using a list to pass the result by reference
```
### Call the function
```python
success=divide_numbers(dividend,divisor,result)
```
### Check the result
```python
ifsuccess:
print("Division successful!")
print("Result:",result[0])
else:
print("Cannot divide by zero!")
```
In this example, we attempt to divide `dividend` (10) by `divisor` (2). Since the division is possible, the `result` list is modified with the calculated result. The `success` variable is `True`, so we print the successful division result. If the divisor were zero, the function would return `False`, and we would print the "Cannot divide by zero!" message.
```
```
# Advanced return values (Option Types)
In Python, there is no built-in implementation of an Option type like in some other programming languages. However, we can create a simple implementation of an Option type using classes. Here's an example:
```python
class Option:
def __init__(self, value):
self.value = value
self.has_value = True
def is_some(self):
return self.has_value
def is_none(self):
return not self.has_value
def get(self):
if self.has_value:
return self.value
else:
raise ValueError("Option is None.")
@staticmethod
def some(value):
return Option(value)
@staticmethod
def none():
return Option(None)
```
In this implementation, we have a class called `Option` that represents an option type. It has an instance variable `value` to store the actual value and a boolean `has_value` to indicate whether the option has a value or is None.
The class provides methods like `is_some()` and `is_none()` to check if the option has a value or is None. The `get()` method returns the value if it exists, otherwise it raises a `ValueError`.
We also have static methods `some()` and `none()` to create instances of the Option class with a value or as None.
Here's an example usage:
```python
# Creating options
option1 = Option.some(10)
option2 = Option.none()
# Checking if options have values
print(option1.is_some()) # Output: True
print(option2.is_none()) # Output: True
# Getting values from options
print(option1.get()) # Output: 10
try:
print(option2.get()) # Raises ValueError: Option is None.
except ValueError as e:
print(str(e))
```
In this example, we create two options, one with a value of 10 and another as None. We then check if the options have values using the `is_some()` and `is_none()` methods. Finally, we retrieve the values using the `get()` method, and handle the case when the option is None by catching the `ValueError` exception.
# Unit tests
Here's an example of how you can write unit tests for a function that checks if a user has a valid email using the `unittest` module in Python:
In this example, we have a function `is_valid_email` that checks if an email contains both an "@" symbol and a "." symbol. We write test cases for this function using the `unittest.TestCase` class. Each test case is defined as a method that starts with the word "test". Inside each test method, we call the function with different inputs and use assertions to verify that the expected result matches the actual result.
## A note on Asserts
The `unittest` module in Python provides several 'assert' functions that are used within test cases to check conditions and make assertions about the behavior of the code being tested. Here are some commonly used assert functions in `unittest`:
1. `assertEqual(a, b)`: This function checks if `a` is equal to `b`. If the values are not equal, an assertion error is raised.
2. `assertTrue(x)`: This function checks if `x` is true. If `x` evaluates to false, an assertion error is raised.
3. `assertFalse(x)`: This function checks if `x` is false. If `x` evaluates to true, an assertion error is raised.
4. `assertIs(a, b)`: This function checks if `a` is the same object as `b`. If the objects are not the same, an assertion error is raised.
5. `assertIsNot(a, b)`: This function checks if `a` is not the same object as `b`. If the objects are the same, an assertion error is raised.
6. `assertIsNone(x)`: This function checks if `x` is None. If `x` is not None, an assertion error is raised.
7. `assertIsNotNone(x)`: This function checks if `x` is not None. If `x` is None, an assertion error is raised.
8. `assertIn(a, b)`: This function checks if `a` is a member of `b`. If `a` is not in `b`, an assertion error is raised.
9. `assertNotIn(a, b)`: This function checks if `a` is not a member of `b`. If `a` is in `b`, an assertion error is raised.
10. `assertRaises(exception, callable, *args, **kwargs)`: This function checks if calling `callable(*args, **kwargs)` raises an exception of the specified type `exception`. If the exception is not raised, an assertion error is raised.
These assert functions, along with others provided by `unittest`, help in writing expressive and comprehensive test cases to verify the correctness of your code.
Read through and run the following code. Here we demonstrate a simple program that manages errors as a returned boolean value, and how we might use this code. We use the input function to simulate getting user info and then call our function to add them to our list of party attendees. This function can fail however due to inviting someone who is under 18.
Run the following code, explore the possibility of adding another failure condition by banning people named 'simon' and 'marshall'. You should only edit the function! You should then be able to run this code and get appropriate messages (using the names simon or marshall should cause an error no matter what age you provide).
```python
# A list of people currently invited to the party
party_goers=['jake','marceline']
# A function to add to the party (returns true on success or false on failure)
definvite_to_party(name,age):
ifage<18:
returnFalse
else:
party_goers.append(name)
returnTrue
# Code to simulate our invite, handling errors as appropriate
n=input("Enter your name: ")
a=int(input("Enter your age: "))
ifinvite_to_party(n,a):
print('Invited '+n+' to the party')
else:
print('Error '+n+' could not be added to the party')
```
> You can check out the section in the notes.md for this week which covers advanced strategies for working with return values.
## 1.2 : Exceptions
If you have a solution for Week 12 task 3, extend that work to raise an exception when a package is attempted to be delivered but the package count is negative.
If you have not got an existing solution for week 12 task 3 then you can attempt the following problem:
Write a program that prompts the user to enter their age. If the age entered is less than 18, raise a custom exception called "UnderAgeException". Handle the exception and display an appropriate error message.
Example Output:
Enter your age: 15
UnderAgeException: You must be at least 18 years old to proceed.
Enter your age: 25
You are eligible to proceed.
To complete this task, the beginner Python user needs to write the code for handling the "UnderAgeException" and display the appropriate error message when the age entered is less than 18.
# Task 2 : (Assessment work)
## 2.1
Identify and discuss potential errors in the problem you have identified for your scenario and write this up in approximately 150 words. For this section, focus on a range of errors and why they are important to be handled; which ones may be high priority for example. What are the acceptable means of failure?
## 2.2
Choose an error handling strategy (either return values or exceptions for example) and implement an example of how you might add error handling in python code, in relation to your problem. Create a small simulation of this error handling strategy and then document your approach in a further 150 words. Discuss your choice for using either return values or exceptions and how you intended the error to be handled.
> Tip: if you are finding this hard to start look at the style of code used in Task 1.1 for inspiration. Note that return value errors may be simpler to work with and produce simpler control flow, however they may not provide as much insight as strategies like exceptions.
# Task 3
## 3.1 : Unit tests
> Note: in this task, we will use some code constructs that have not been introduced yet. Use the example code as a framework and we will explore the use of import and asserts for example in future sessions.
In the lecture we have discussed different testing strategies. For more practical exploration we will focus on unit testing. Python has a built in module for unit testing. Here's an example of it's use, by importing the built-in `unittest` module in Python:
```python
importunittest
# The function in your code to be tested
defadd_numbers(a,b):
returna+b
# Create a test case class, containing functions for each test
classTestAddNumbers(unittest.TestCase):
deftest_add_positive_numbers(self):
result=add_numbers(2,3)
self.assertEqual(result,5)
deftest_add_negative_numbers(self):
result=add_numbers(-2,-3)
self.assertEqual(result,-5)
deftest_add_zero_to_number(self):
result=add_numbers(5,0)
self.assertEqual(result,5)
# Run the tests
unittest.main()
```
In this example, we have a function `add_numbers` that adds two numbers. We write test cases for this function using the `unittest.TestCase` class. Each test case is defined as a method that starts with the word "test". Inside each test method, we call the function with different inputs and use assertions to verify that the expected result matches the actual result.
To run the tests, we use the `unittest.main()` function, which discovers all the test cases defined in the file and runs them.
When you run this script, the output will show whether the tests pass or fail. If all the assertions pass, you'll see an output like this:
If any assertion fails, you'll see a detailed error message indicating which test case failed and what the expected and actual results were.
**Run and test the above code and make a change in the add_numbers function to cause the test cases to fail.**
## 3.2
Add two more test functions to the example above to test for adding zero to zero and another for adding a negative and a positive.
# Task 4 (Assessment work)
## 4.1
Choose a function from the code around your scenario and write a unit test that tests the function in as many ways as you can think of. Try to focus on a function that can possibly fail in more than one way.
In your report document and discuss this unit test, describing what each test checks and how this is checked (approximately 150 words).
## 4.2
Discuss further how other testing strategies can applied to your problem. What components require particular forms of testing and how can this help to ensure that your problem can be solved.