Lab 8
3 November, 2023 - Classes and Objects
- PyCharm recap
- Overview of This Lab
- Exercise 1: Memory problems and 2D lists (standard)
- Exercise 2: Side effects (challenge, medium)
- Exercise 3: First ten digits (standard)
- Exercise 4: Shapes and classes (standard)
- Exercise 5: 2Dvector classes (standard)
- Exercise 6: Power functions and classes (standard)
Welcome to week 9, lab 8!
PyCharm recap
This week we continue using PyCharm and running code in the terminal. In lab 7 you can find information about: installing anaconda, opening projects, configuring unittests, reading unittests results, and debugging.
Overview of This Lab
This week we have 6 different exercises that range in difficulty:
- Exercise 1: Memory problems and 2D lists (standard)
- Exercise 1a: Understand the problem (standard)
- Exercise 1b: Use numpy (standard)
- Exercise 1c: The ambiguity of multiplying a list (standard)
- Exercise 1d: How to make a 2D list (challenge, medium)
- Exercise 2: Side effects (challenge, medium)
- Exercise 3: First ten digits (challenge, medium)
- Exercise 4: Shapes and classes (standard)
- Exercise 5: 2Dvector classes (standard)
- Using 2Dvectors: shortest path (challenge, hard)
- Exercise 6: Power functions and classes (standard)
All students, regardless of experience, should be able to complete the 4 standard exercises. Whether you complete all four is up to you.
The 4 exercises marked as “challenge” provide additional practice for interested students. We believe that any student in this class who completes the standard exercises should be able to complete the “medium” difficulty challenge.
For more advanced students, you might find the more difficult shortest path exercise using the 2DVectors to be more interesting. Feel free to skip the standard exercises if you think they are insufficiently challenging. You can also look at previous labs for challenging exercises.
If you feel that this week’s challenges are too hard for you, feel free to do a challenging exercise from last week which should be easier now that you know more.
Exercise 1: Memory problems and 2D lists (standard)
In this exercise, we will show you why multiplying a list is a bad idea and dangerous. But more important we want to emphasise that coding is thinking about what happens in your memory. While some code looks correct when reading it as a natural language (e.g. English), it does not give the right instructions to your computer. To concluded be always aware of what the code does and specifically think about if you copy the value(s) to new memory or just creates a reference to the same memory.
Tip: If you are unsure about the behavior of your code, and you want to make sure that it is a copy, you can use import copy and then some_variable = copy.deepcopy(...)
Exercise 1a: Understand the problem (standard)
Go to the following page and try to understand what goes wrong.
Hint: Multiplying a list does not create a copy in memory, but can ints, floats, bool, strings etc. be changed. What do these data types have in common? And what happens when you try to multiply a list with such datatypes?
Exercise 1b: Use numpy (standard)
In exercise 1a, we saw that building a 2D list can create unexpected results. In exercise 1c, you will see why it is even een worse idea to construct a 2D list by multiplying a list. Finally, in exercise 1d, you will try to implement a correct way of constructing a 2D list. While these exercise can help you understand what happens in memory, the second lesson in the whole exercise is do not use list when you use one data type, and you know the length/shape/size it needs to be. In this case, always use numpy arrays and never use lists. This solves also the problem of even trying to create 2D lists because, as we saw last week, numpy has the functions: np.zeros, np.ones and np.empty.
Tip: You can even use your own classes in arrays by using the argument dtype.
Open exercise 2D_data_structures.py and complete the function exercise_1a_with_2D_arrays.
Exercise 1c: The ambiguity of multiplying a list (standard)
In exercise 1a, we learned that multiplying a list is a bad way to create a 2D list. However, even multiplying a single list can be a big problem. Look at the code below and try to answer the following question: Do we know what this code does? or is it ambiguous and only when to code is run (at runtime) we find out what the actual effect is?
new_list = [some_variable] * 4
Below you will find a bit more fletched out piece of code. Is it now possible to answer what the code will do? or is it still ambiguous?
>>> print(some_variable)
-3
>>> new_list = [some_variable] * 4
>>> new_list[0] = abs(new_list[0])
>>> print(new_list) #[3, -3, -3, -3] or [3, 3, 3, 3]?
...
The answer to these question is that it is ambiguous, but try to understand why. Also, note that even with more context and print statements, it is still unclear what the code does. This means that the code is unreadable and therefore very bad.
To show an example where the code above does two different things, we made two examples. link 1 only makes the first item in the list positive. link 2 makes all items in the list positive. You can argue that seeing how some_variable is defined can solve the problem. However, when working in large code bases some_variable can be defined anywhere, for example, in a different file or somewhere you don’t know.
Exercise 1d: How to make a 2D list (challenge, medium)
As explained in exercise 1b, it is generally not a good idea the use lists if you can use numpy arrays. However, it is a good exercise to think about how you could make the code work in exercise 1a. Therefore, in this exercise you will fix the code in 1a by using lists.
Open exercise 2D_data_structures.py and complete the function exercise_1a_with_2D_lists.
Exercise 2: Side effects (challenge, medium)
This exercise continues with exploring how side effects work and how to create or avoid them. This is also a good exercise for a simple example of creating your own functions. In the exercise, we will repeat exercise 3: enumerate multiply_index_value from lab 6. However, it is extended a bit by not only asking the complete multiply_index_value but also use it to calculate the difference between the original list and the outcome of multiply_index_value.
In this exercise you will no longer only complete one function, but you need to complete the main function that can use any number of functions. This is synonymous with writing small scripts. Open exercise side_effects.py and follow the instructions.
Exercise 3: First ten digits (standard)
In this exersice, we will work out the first ten digits of the sum of ninety-nine 50-digit numbers in the file first_ten_digits.txt. We will use two strategies to do this. First, we will open the file as a textfile and transform the strings into numbers. Second, we use numpy to read the file and continue with a numpy array.
It is important to also close a file when you are opening it using code. This can be seen similarly as just opening a file in a program and not closing it. What happens if you open a file 50 times and you adjust one or if you want to delete the file but some program is using it (having it open). Therefore it is important to make sure that after your script has run all files are closed. Even if your program terminates unexpected due to for example a bug.
To make sure a file is closed python has a specific syntax which is as follows:
with open(file_name, file_mode) as f:
# This would print the whole file.
print(f.read())
# This loops through all the lines in the file.
# Equivalent to: for line in f.readlines().
for line in f:
pass
As soon as you do not need the file anymore you can continue outside the with statement. This then closes the file.
When you use np.loadtxt the file is automatically closed after this line of code.
Open exercise first_ten_digits.py and follow the instructions.
Exercise 4: Shapes and classes (standard)
In this exercise, you will create a general 2D shape class that can calculate the circumference and area for several shapes. This is possible because most shapes have almost the same formula and if we ignore naming we can just say we have length1 and length2. These lengths can be radius, height, base, etc.
Open exercise shape.py and follow the instructions.
Just a quick discussion note, if you do not understand polymorphisms please skip this note:
Generally this exercise is used to explain and implement polymorphisms with a base class and for each shape a separate class. However, often due to polymorphisms links between classes is mist. In this exercise, we made one class that rules them all. This is possible because the calculations for area and circumference is very similar for all 2D shapes. If polymorphisms is not needed many believe nowadays that a single class is better.
Exercise 5: 2Dvector classes (standard)
In this exercise, we continue learning about classes. Here, you will implement a 2Dvector class, that creates vector objects. A 2Dvector can for example be a coordinate with an x and y value.
Start by implementing the __init__, add, subtract and distance method.
In this exercise, we will also have a look at how we can use the operators + and - with our own classes. This is done by implementing the __add__ and __sub__ methods. Use the previous implemented function to complete these functions. Do not write how to add two 2Dvector again.
Open exercise vector2D.py and follow the instructions.
Using 2Dvectors: shortest path (challenge, hard)
In this exercise, you will find the shortest path between a series of coordinates.
Open exercise vector2D.py and complete main using the class vector2D that you made in the previous exercise.
Exercise 6: Power functions and classes (standard)
In this exercise, we will learn what it means if a variable is callable and we will make power functions.
So far we only have seen callable objects when we made a function. An example of this is def power(): this creates an object power that is callable and when called “()” it executes the function __call__ and returns something. Calling a function means putting brackets “()” behind it. Typing def power(): is just a way of making an object power with only the __call__ function. Everything you type in a definition is in the __call__ function.
Tip: If you ever get the error this function is not callable then you probably put brackets “()” behind a non-callable variable.
Open exercise power.py and follow the instructions.