Lab 1: OOP

  • Due before 23:59 Wednesday, August 31
  • Submit using the submit program as 301 lab 01.

1 OOP Practice

Object-Oriented Programming is a really big part of implementing data structures, so it's important that we practice before our first Project.

Part One: A very simple class (30/100)

This class will be trivial in functionality, but will serve as an example for class structure. In a file called lab1.py, make a class called Adding. This class should have:

  • A constructor (sometimes called an initializer in Python), which takes two arguments (they'll be integers), and stores them as fields in the class.
  • A function called add_third, which takes an additional integer, and adds the three numbers together, before returning them (not printing them).

Copy-paste the following code into a file called lab1_test1.py, in the same directory as lab1.py. Running python3 lab1_test1.py should run your class correctly, and print the number 18:

1
2
3
4
5
6
from lab1 import Adding
 
if __name__ == "__main__":
    obj = Adding(5, 3)
    added = obj.add_third(10)
    print(added)

Part Two: The __str__ Method (50/100)

Suppose we create a class called AClass, and make an object of that type, like so: obj = AClass(). We then call print(obj). What will print?

A right answer here is "I have no idea." The reason you have no idea is because you don't even know what data is stored within this object, so how can you possibly know what the right way to print it to the screen is? Python is in the same bind; this is your class, how can it know what you want to display?

Fortunately, there's a way of telling Python how to print things: the __str__ function (special functions in Python usually start and end with double-underscores, to avoid accidental name collisions). Whenever an object needs to be printed, or otherwise converted to a string, Python goes looking for that object's __str__ function; if it exists, it runs that function, and that function returns a string (it does not print the string, it just returns it); the returned string is then printed, or used as the string representation.

Add a __str__ method to your Adding class, so that when an object of that type is printed, you get output that looks like this:

Adding: (firstnum, secondnum)

The following testing code:

1
2
3
4
5
from lab1 import Adding
 
if __name__ == "__main__":
    obj = Adding(3,9)
    print(obj) #Calls __str__ and prints what is returned

should result in the following output:

$ python3 lab1_test2.py 
Adding: (3, 9)

Similarly, the following testing code:

1
2
3
4
5
6
7
from lab1 import Adding
 
if __name__ == "__main__":
    obj = Adding(3,9)
    strVersion = str(obj) #Calls __str__, and stores what is returned
    print('here')
    print(strVersion)

Should result in the following output (notice how the object is printed AFTER 'here' due to the fact that __str__ returns, not prints, the string):

$ python3 lab1_test3.py 
here
Adding: (3, 9)

2 Part Three: Keeping Track of Network Resources (100/100)

We're going to make a little program to keep track of which processes are running on a network and what their memory requirements are. Here's a description of the objects involved. A Process is an object which contains a name, and an int which indicates the number of KB of memory required to run that Process. A Machine is an object with a name, a list of Processes running on that Machine, and an int indicating the maximum amount of available memory in KB. A Network contains a list of Machines.

Description of Classes

Place all three of these classes in a single file named network.py. You can download a testing file here, which can help you test after finishing each class. I won't usually give you a testing file like this, and will require you to make your own, to have full confidence in what you're turning in. Take note of how I wrote it!

Process has two fields, name (a String) and memoryReq (an int). As far as methods, it should have:

  • a constructor (sometimes called an initializer), which takes both a String and an int, in that order, and
  • __str__, which returns a string of the form processName: memoryReqKB (for example, "aName: 5KB")

Machine has three fields, name (a String), processList (a list of Processes running on that machine), and totalMem (an int). For methods, it should have:

  • a constructor, which takes a name and totalMem as arguments, and sets processList to be an empty list,
  • addProcess, which takes a Process as an argument, and appends it to processList,
  • availableMemory, which takes no arguments (aside from self), and returns the result of subtracting the total amount of memory required by the Processes in processList from totalMem, and
  • __str__, which returns a string describing the machine of the form
    machineName, totalMemKB
      aProcessName: someKB
      anotherProcessName: someOtherKB
    

Network has as a field a list of Machines named machines. For methods, it should have:

  • a constructor, which takes no arguments (again, aside from self), and sets machines to be an empty list;
  • addMachine, which takes a Machine as an argument, and appends it to the list machines;
  • addProcess, which takes a Process as an argument, figures out which Machine in machines has the most available memory (by calling their availableMemory method), and assigns the Process to that Machine (using its addProcess method); and
  • __str__, which returns a string describing the entire network, of the form
    Network:
    machineName, totalMemKB
      aProcessName: someKB
      anotherProcessName: someOtherKB
    anotherMachineName, totalMemKB
      aProcessName: someKB
      anotherProcessName: someOtherKB
    

3 Optional Extensions

(Note the rest of this lab is an optional exercise for your enrichment, to challenge you and learn something and earn the admiration of your professor and peers. It might be tangibly useful if e.g. you are going to ask me to write you a recommendation letter at some point in the future, but mostly it'll just make you a better programmer!)

Opt1: Exceptions

There's one thing that can go wrong in the "Network" above, even if all of the arguments have the right types and would otherwise make sense: The network might not have any machine with enough memory for the process that you want to add!

This is a good example of where it would make sense to throw a Python exception. So for this part, I want you to add an exception class called OutOfMemory, and make it so this exception is thrown whenever the addProcess method of Network finds that there isn't enough memory in any machine to handle the situation. It would probably be a good idea to make the same thing happen for the addProcess method in Machine as well.

I'm not going to tell you exactly how to do this. Use Google, ask questions, and think about the simplest and most straightforward way to do this in Python. You'll definitely have to make a new class for your exception and put a throw command somewhere.

Opt2: Placing multiple processes

Still want more to do? OK then!

Add one more method to the Network class called multiProcess, which takes a list of processes as its single argument, and adds all of them to various machines in the network.

One way to do this (and a good way to start) would be to add them one at a time, using the addProcess method you already wrote. But this might not always lead to the very best way of using the memory resources evenly. Can you think of anything better? Try it!