diff --git a/overviews/week14.md b/overviews/week14.md index e15440e6677964721f9e15217686dc81976d7267..ecbeb3a5c8eae4873bcac845fab66ed8af73910e 100644 --- a/overviews/week14.md +++ b/overviews/week14.md @@ -19,6 +19,7 @@ All tasks up to week 12! If you have not done week 11's work on your assessment - The tasks provided in `ctap-resources/practicals/week14/tasks.md` ## Resources +- **This weeks [notes.md](https://gitlab.uwe.ac.uk/ctap/ctap-resources/-/blob/main/practicals/week14/notes.md?ref_type=heads) has quite comprehensive reference examples!** - [Chapter 12 of Computational thinking: a beginner's guide to problem-solving and programming](https://blackboard.uwe.ac.uk/webapps/blackboard/content/listContentEditable.jsp?content_id=_9740235_1&course_id=_358486_1&mode=reset) - [practicals/week12/tasks.md]() - [Slides](https://go.uwe.ac.uk/ctapSlides) diff --git a/practicals/week12/tasks.md b/practicals/week12/tasks.md index ad09ea0f121b534770f7f899c0f2569a8c65642e..44c9fe5721e2a6fdb776abfeb10368f721507a24 100644 --- a/practicals/week12/tasks.md +++ b/practicals/week12/tasks.md @@ -47,7 +47,7 @@ Based on your work from task 1, implement an code that implements the following > You may run into errors such as allowing the count of packages to become negative. If you reach this point, that is acceptable and you may save this solution for futures weeks (where we look at error handling) -# Task 4 +# Task 4 (Assessment work) Begin to implement an algorithm for your problem, focusing on using the ideas you have abstracted in task 2. You should ensure you are working with your tutor on this problem to get support. diff --git a/practicals/week13/recap-practical-1.md b/practicals/week13/recap-practical-1.md new file mode 100644 index 0000000000000000000000000000000000000000..c2dc31103e311ebe0fe656fde53d6ea6c0050e61 --- /dev/null +++ b/practicals/week13/recap-practical-1.md @@ -0,0 +1,28 @@ +Hi everyone, + +As informed by the module feedback we did last week, this week is a recap/ catchup week. + +Two key things were indicated as needing clarification; python programming and the assessment. + +Lecture 1 this week will allow us to recap python. +In the practicals you should then work through the previous weeks programming and work on anything you missed and/or did not understand. + +You are expected to work with your practical tutor on this and address something that you did not previously understand. If you are finding python/coding challenging and are new, **focus on week 8** + +As a reminder + +Week 8 Covers: +- Printing +- Variables +- Conditionals +- Loops +- Functions + +Week 9 Covers: +- Conditionals +- Boolean algebra + +Week 12 Covers: +- More complex data structures +- Classes +- Tuples, sets and lists \ No newline at end of file diff --git a/practicals/week13/recap-practical-2.md b/practicals/week13/recap-practical-2.md new file mode 100644 index 0000000000000000000000000000000000000000..ad8a2abb9074e7a78e097c98593c4a81564fa84c --- /dev/null +++ b/practicals/week13/recap-practical-2.md @@ -0,0 +1,25 @@ +Hi everyone, + +As informed by the module feedback we did last week, this week is a recap/ catchup week. + +Two key things were indicated as needing clarification; python programming and the assessment. + +Lecture 2 this week will include an overview and dive into (with examples) of the +assessment work. This lecture is exceptionally important, **attendance will be reviewed for this session in particular** and you should note that attending will allow you to vote on how we approach the next week of work. + +You should attempt to prepare prepare some of your assessment work to be reviewed in the practical session by your tutor. This practical is a chance to catch up with the work across the previous weeks. So far you should have worked on the following: + +- A decomposition tree (diagram - which is included into your report as [described here](https://uwe.cloud.panopto.eu/Panopto/Pages/Viewer.aspx?id=a60f118a-4071-401e-bf9c-b0a600a85760&instance=Blackboard)) +- A discussion of your decomposition (150 words) +- A discussion of the feasibility of solving your problem (150 words) +- A description of some functional and non functional requirements of your problem (150 words) +- A flow diagram (diagram - which is included into your report as [described here](https://uwe.cloud.panopto.eu/Panopto/Pages/Viewer.aspx?id=a60f118a-4071-401e-bf9c-b0a600a85760&instance=Blackboard)) +- Describe your process of creating a flow diagram for your chosen problem. +- Implement some code that simulates the problem for which you made a flow diagram +- For your problem create an entity relationship diagram (diagram - which is included into your report as [described here](https://uwe.cloud.panopto.eu/Panopto/Pages/Viewer.aspx?id=a60f118a-4071-401e-bf9c-b0a600a85760&instance=Blackboard)) +- Program a representation of this diagram +- Write up your process of modelling the data for your problem (150 words) +- Further refine your data structures using tuples, sets, lists, dictionaries and classes +- Further discuss your data model (150 words) +- Begin to implement and discuss the algorithm for your problem (300 words) + diff --git a/practicals/week14/code.py b/practicals/week14/code.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/practicals/week14/notes.md b/practicals/week14/notes.md new file mode 100644 index 0000000000000000000000000000000000000000..32dbd324ecff231f9108cf8cf5ac15c591fecce1 --- /dev/null +++ b/practicals/week14/notes.md @@ -0,0 +1,198 @@ +# Exceptions + +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 +except ZeroDivisionError: + # Handle the specific exception that was raised + print("Error: Division by zero is not allowed.") +except Exception as e: + # 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) + +### Alternate resources + +- [Python Docs](https://docs.python.org/3/tutorial/errors.html) +- [Python Error handling with exceptions (youtube)](https://www.youtube.com/watch?v=NIWwJbo-9_8) +- [Advanced exceptions (youtube)](https://www.youtube.com/watch?v=ZUqGMDppEDs) + +# Return Values + +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 +def divide_numbers(dividend, divisor, result): + if divisor == 0: + return False + else: + result[0] = dividend / divisor + return True +``` + +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 +if success: + 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: + +```python + +import unittest + +# Function to be tested +def is_valid_email(email): + if "@" in email and "." in email: + return True + else: + return False + +# Test case class +class TestIsValidEmail(unittest.TestCase): + + def test_valid_email(self): + result = is_valid_email("test@example.com") + self.assertTrue(result) + + def test_invalid_email_no_at_symbol(self): + result = is_valid_email("testexample.com") + self.assertFalse(result) + + def test_invalid_email_no_dot(self): + result = is_valid_email("test@examplecom") + self.assertFalse(result) + + def test_invalid_email_no_at_symbol_and_dot(self): + result = is_valid_email("testexamplecom") + self.assertFalse(result) + +# Run the tests +unittest.main() +``` + +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. \ No newline at end of file diff --git a/practicals/week14/tasks.md b/practicals/week14/tasks.md new file mode 100644 index 0000000000000000000000000000000000000000..187e13b5b27a3f9f2a0328417d2b6c96dab4c791 --- /dev/null +++ b/practicals/week14/tasks.md @@ -0,0 +1,128 @@ +# Week 14 Tasks for practicals + +# Task 1 : Error handling +## 1.1 : Return values + +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) +def invite_to_party(name, age): + if age < 18: + return False + else: + party_goers.append(name) + return True + +# Code to simulate our invite, handling errors as appropriate +n = input("Enter your name: ") +a = int(input("Enter your age: ")) + +if invite_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 +import unittest + +# The function in your code to be tested +def add_numbers(a, b): + return a + b + +# Create a test case class, containing functions for each test +class TestAddNumbers(unittest.TestCase): + + def test_add_positive_numbers(self): + result = add_numbers(2, 3) + self.assertEqual(result, 5) + + def test_add_negative_numbers(self): + result = add_numbers(-2, -3) + self.assertEqual(result, -5) + + def test_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: + +```markdown +... +---------------------------------------------------------------------- +Ran 3 tests in 0.001s + +OK +``` + +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.