generated from carpentries/workbench-template-md
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathEpisode3.Rmd
291 lines (208 loc) · 12.2 KB
/
Episode3.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
---
title: "Write Robust Code"
teaching: 15
exercises: 8
---
:::::::::::::::::::::::::::::::::::::: questions
- How to make code user-friendly?
- What practices can you adopt to ensure your Python code is robust and less prone to failures?
- Why is comprehensive error handling crucial for code reliability, and how can it be implemented effectively?
::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::: objectives
- Write Python User-Friendly code using docstrings
- Implement robust coding practices using Assertions and Raising Exceptions
- Develop and Apply Effective Error Handling Techniques
::::::::::::::::::::::::::::::::::::::::::::::::
Much like a carpenter uses the right tools to create sturdy and beautiful furniture, a programmer employs the right techniques to create robust code. This episode will equip you with the tools for building strong, error-resistant code.
## Making Python code User-Friendly
Consider the following code
``` python
def calculate_rectangle_area(width, height):
area = width * height
return area
```
At a glance, we can understand the purpose and functionality of this code. The function name `calculate_rectangle_area` is descriptive and tells us exactly what the function is meant to do. Similarly, the variables `width` and `height` are intuitively named, so we know they represent the dimensions of a rectangle. Even without deep programming knowledge, someone can infer what this code does. While the code is well-written, is this level of clarity sufficient for someone to run the code successfully?
One important aspect missing from the example is the information about variable types. Without specifying that `width` and `height` should be numerical values (integers or floats), someone may inadvertently provide invalid inputs (e.g., strings) that would cause the function to fail.
:::::::::::::::::::::::::::::::::::::challenge
Write a code that uses the function calculate_rectangle_area with the following values for width and height and observe the function's behaviour. Call the function within a Python terminal and print the results.
1. width = 5.2 and height = 4.3
2. width = 5 and height = '4'
3. width = '5' and height = '4'
:::::::::::::::::::::::::::::::::::::solution
```python
# Case 1. The code gives the right result
>>print("width = 5.2 and height = 4.3 (both numerical):\n area = ",calculate_rectangle_area(5.2, 4.3))
width = 5.2 and height = 4.3 (both numerical):
area = 22.36
# Case 2. The code multiplies the string (height) by 5, meaning that the string is repeated 5 times.
>> print("width = 5 and height = '4' (both numerical):\n area = ",calculate_rectangle_area(5, 4))
width = 5 and height = '4' (one numerical and one string)
area = 44444
# Case 3. The code raises a TypeError
>> print("width = '5' and height = '4' (one numerical and one string):\n area = ",calculate_rectangle_area('5', '4'))
[...]
TypeError: can't multiply sequence by non-int of type 'str'
```
::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::
## What can go wrong?
Users who interact with code without fully understanding it may make errors, causing the code to behave unexpectedly. In the best-case scenario, the execution may be disrupted, prompting the user to identify and correct the mistake. In the worst-case scenario, the code may execute without any visible errors but produce incorrect results, a situation known as a *silent failure*. Such failures often go unnoticed and are difficult to diagnose and fix.
Although we cannot predict what errors might occur, we can add in-line documentation that explains how to use a function or a piece of code and control misuse by returning errors or messages that allert the user when something goes wrong.
### Step 1: Tell how to use your code
It is impossible to predict how someone else will use our code. The best we can do is add extra information in the form of comments or, even better, *docstrings*. In Python, docstrings are string literals after defining a function, method, class, or module. They are used to document the specific segment of code they are associated with.
The advantage of using docstrings is that they are used by the Python built-in function `help` to display the information without having to navigate through the source code.
A better version of the function above can be:
``` python
def calculate_rectangle_area(width, height):
""" Calculate the area of a rectangle, knowing its height and width
Args:
width (float): The width of a rectangle
height (float): The height of a rectangle
Returns:
float: area of the rectangle
"""
area = width * height
return area
```
In the docstring, we briefly describe what the function does, the arguments and their types, and the type of the returning variables.
A user can then call the `help` function to understand how to use it.
```python
>> help(calculate_rectangle_area)
calculate_rectangle_area(width, height)
Calculate the area of a rectangle, knowing its height and width
Args:
width (float): The width of a rectangle
height (float): The height of a rectangle
Returns:
float: area of the rectangle
```
### Step 2: Handle errors
The goal is to ensure that the code runs correctly and provides informative feedback to users when unexpected issues arise. Using comments and docstrings reduces the likelihood that code is used incorrectly. Sometimes, errors and bugs are somewhere else in the code, and they can be propagated without proper handling. These errors might be due to unexpected conditions, invalid inputs, hardware failures, or bugs in the code.
In such scenarios, error handling becomes essential. By applying error handling techniques, developers can catch and resolve issues as they occur, provide meaningful error messages to users, and take appropriate actions to maintain code integrity.
In Python, there are different techniques one can use to handle errors and exceptions properly. Here, we will focus on **assertions** and **error raising**.
#### Assertions
An *assertion* is a statement that check if a given condition is true. If this is not the case, it returns an error message. The syntax is
``` python
assert Condition, 'Error message'
```
```python
> x = -1
> assert x>0, 'x must be greater than 0' #check if the condition x>0 is true
----> 1assert x>0, 'x must be greater than 0'
AssertionError: x must be greater than 0
```
Assertions can check multiple statements using the `and` and `or` operator
```python
> x = 3
> assert x>0 and x<2 , 'x must be between 0 and 2'
----> 1assert x>0 and x<2, 'x must be between 0 and 2'
AssertionError: AssertionError: x must be between 0 and 2
```
Functions that return a boolean output (True or False) can also be used. For example, a useful Python function that checks a variable type is `isinstance`. This function returns `True` if the type of a variable is of a given type or `False` otherwise. For example:
```python
> x = 3.5
> isinstance(x,float) # check if x is of type float.
True
```
The `isistance` function can be used together with `assert` to check a variable is of the right type. For example:
```python
> x = "3.5"
> assert isinstance(x,float), "x must be of type float"
[...]
AssertionError: x must be of type float
```
:::::::::::::::::::::::::::::::::::::challenge
Add assertion to the following code to check that `width` and `height` are of type `float`.
```python
def calculate_rectangle_area(width, height):
""" Calculate the area of a rectangle, knowing its height and width
Args:
width (float): The width of a rectangle
height (float): The height of a rectangle
Returns:
float: area of the rectangle
"""
area = width * height
return area
print("width = 5.2 and height = 4.3 (both numerical): area = ",calculate_rectangle_area(5.2, 4.3))
print("width = 5 and height = '4' (both numerical): area = ",calculate_rectangle_area(5, 4))
```
:::::::::::::::::::::::::::::::::::::solution
```python
def calculate_rectangle_area(width, height):
""" Calculate the area of a rectangle, knowing its height and width
Args:
width (float): The width of a rectangle
height (float): The height of a rectangle
Returns:
float: area of the rectangle
"""
assert isinstance(width, float) and isinstance(height, float), "Input must be of type float"
area = width * height
return area
```
:::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::
#### Raise
Another way to throw exceptions and errors is to use the Python `raise` keyword. With `raise`, you can trigger both built-in and custom exceptions. It is similar to `assert`, but it is used to signal runtime errors. The `raise` keyword is generally used within an if-statement that checks for a specific condition.
```python
x = -1
if x < 0 : #make sure that x is always greater than 0
raise Exception("x must be greater than 0")
1 if x < 0 : #make sure that x is always greater than 0
---> 3 raise Exception("x must be greater than 0")
Exception: x must be greater than 0
```
The code raises a general `Exception` when the variable is less than zero. `Exception` is a built-in Python error type indicating a generic error. Python has different [error types](https://docs.python.org/3/library/exceptions.html) that can be used in specific situations.
The most common are:
| Error Type | Description |
|----------------------|----------------------------------------------------------------------------------------------|
| `TypeError` | Happens when an operation or function is applied to an object of inappropriate type. |
| `NameError` | Occurs when a local or global name is not found. |
| `ValueError` | Raised when a function receives an argument of the correct type but inappropriate value. |
| `IndexError` | Happens when trying to access an index that is out of the range of a list or tuple. |
| `KeyError` | Raised when trying to access a dictionary with a key that does not exist. |
| `ZeroDivisionError` | Raised when dividing by zero. |
| `FileNotFoundError` | Occurs when trying to open a file that does not exist. |
| `RuntimeError` | Raised when an error does not fall under any other category. |
:::::::::::::::::::::::::::::::::::::challenge
Let us consider the code below and modify it so that it raises an error if any of the input parameters are less than zero.
What is the more appropriate error type?
```python
def calculate_rectangle_area(width, height):
""" Calculate the area of a rectangle, knowing its height and width
Args:
width (float): The width of a rectangle
height (float): The height of a rectangle
Returns:
float: area of the rectangle
"""
area = width * height
return area
#you can test running the following code
area = calculate_rectangle_area(-5.1, 3.2)
```
:::::::::::::::::::::::::::::::::::::solution
In this case, the code should raise a `ValueError`.
```python
def calculate_rectangle_area(width, height):
""" Calculate the area of a rectangle, knowing its height and width
Args:
width (float): The width of a rectangle
height (float): The height of a rectangle
Returns:
float: area of the rectangle
"""
if width < 0 or height < 0:
raise ValueError("width and height must be positive values")
area = width * height
return area
```
:::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::: keypoints
- *Docstrings* provide clear documentation of your code and improve its readability and usability.
- *Error handling techniques* help build robust code. The idea is to anticipate potential errors and to implement mechanisms to handle them gracefully.
- *Assertions* ensure that certain conditions are met during code execution.
- By *raising specific exceptions* (e.g. `TypeError`, `ValueError`), you can not only control the behaviour of your code when things go wrong but also inform the user that something is right.
::::::::::::::::::::::::::::::::::::::::::::::::