SmithLogo

CSC 111

Introduction to Computer Science Through Programming

Smith Computer Science



Lecture Notes 24: More about Classes and Objects





Classes Recap

Sometimes we want to build a complex relationship between elements by saving data and functions that modify it.

For example, Say we want to build a Fruit-Focused-Social-Network called Fruit-Buddy, or FB

A simple sketch would look like this:

fb-sketch



Since we want to Represent people and their interests with data, there are a LOT of ways to construct these complex objects (using id's, names, ...)

fb-sketch



We could use the existing lists, tuples, and dictionaries, but sometimes that gets a little cumbersome.

Example: Building the Social Network FB (Fruit Buddies)

The following is a naive way of building FB (the network of buddies that like fruits)

 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
  # constructing social network FB: Fruit Buddies
  def main():
    # tuple with: user id; name; favorite thing
    u_data = (101, "Alice", "Apples")
    u_contacts = []
    FB_user_01 = [u_data, u_contacts]

    u_data = (102, "Bob", "Bananas")
    u_contacts = []
    FB_user_02 = [u_data, u_contacts]

    u_data = (103, "Cathy", "Clementines")
    u_contacts = []
    FB_user_03 = [u_data, u_contacts]

    u_data = (104, "Dan", "Dates")
    u_contacts = []
    FB_user_04 = [u_data, u_contacts]

    # first way to add buddies
    FB_user_01[1].extend([101, 102, 103])

    FB_user_02[1].extend([101, 102, 103])

    # second way to add buddies
    add_buddy(FB_user_03, FB_user_02)
    add_buddy(FB_user_03, FB_user_03)

    add_buddy(FB_user_04, FB_user_04)

    FB_users = [FB_user_01, FB_user_02, FB_user_03, FB_user_04]

    print_buddies(FB_users)

  #adding user2's id to user1's list of id-buddies
  def add_buddy(user1, user2):
    user1[1].extend([user2[0][0]])

  # prints all networked friends
  def print_buddies(all_users):
    for user in all_users:
      print("Buddies of {} :".format(user[0][1]), end = " ")
      
      for id in user[1]:
        print(id, end=" ")
      if len(user[1]) == 1:
        if user[1][0] == user[0][0]:
          print ("** Sad Trombone **")
      print()  

  if __name__ == "__main__":
    main()


Activity 1 [2 minutes]:
Run it in the Python Tutor and look at how it is organized in memory.



What is going on when you run this code?



Note that when we want to interact with our complex objects, or when building functions to make our life easier, we have to use combinations of "square bracket notation" accesses (user[1][0]) to access the desired data.

Also, if we want to modify the structure for each object, we need to go in and change each one.



Building the same netweork, with classes

Now, let's try a different approach:

 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
  class Fruit_Buddy:

    def __init__(self, id, name, fav_fruit, buddy_list):
      self.id = id
      self.name = name
      self.fav_fruit = fav_fruit
      self.buddies = buddy_list

    def add_buddy(self,other):
      # adding the other's id (this can be made better!)
      self.buddies.append(other.id) 

    def add_buddy_list(self,buddy_list):
      for bud in buddy_list:
        self.add_buddy(bud)   

    def print_buddies(self):
      print("Buddies of {} :".format(self.name), end = " ")
      for bud in self.buddies:
        print(bud, end=" ")
      if len(self.buddies) == 1:
        if self.buddies[0] == self.id:
          print ("** Sad Trombone **")
      print()      

  # constructing social network FB: Fruit Buddies
  def main():

    # the constructor can be improved with default vals
    FB_user_01 = Fruit_Buddy(101, "Alice", "Apples",[])
    FB_user_02 = Fruit_Buddy(102, "Bob", "Bananas",[])
    FB_user_03 = Fruit_Buddy(103, "Cathy", "Clementines",[])
    FB_user_04 = Fruit_Buddy(104, "Dan", "Dates",[])

    # first way to add buddies
    FB_user_01.add_buddy_list([FB_user_01, FB_user_02, FB_user_03])
    FB_user_02.add_buddy_list([FB_user_01, FB_user_02, FB_user_03])
    FB_user_03.add_buddy_list([FB_user_02, FB_user_03])
    FB_user_04.add_buddy_list([FB_user_04])

    FB_user_01.print_buddies()
    FB_user_02.print_buddies()
    FB_user_03.print_buddies()
    FB_user_04.print_buddies() 

  if __name__ == "__main__":
    main()


Constructors!

The __init__ method is a constructor!

It makes the internal variables and assigns any parameters or default values to them

Lines 30-33 make use of these to build our OBJECTS.

Note that lines 36-39 pass in lists of object references to each object and they take care of "adding their friends".

We can also make ad-hoc (custom) print methods for our objects (called in lines 41-44).

Activity 2 [2 minutes]:
Check it out step-by step in the Python Tutor




Default Values

One may add default values to the class attributes by assigning them to the __init__ method's attributes,

However, you must place the parmeters with default values AFTER those without (right to left)

For example (DONT ADD TO THE RUNNING CODE YET):

    def __init__(self, buddy_list=None, id=-1, name="NA", fav_fruit="meh"):
      self.id = id
      self.name = name
      self.fav_fruit = fav_fruit
      if buddy_list == None:
        self.buddies = []
      else:
        self.buddies = buddy_list


What else would we need to change if we add these default values?
(Wait; then Click)

The calls in lines 30-33 MUST have the order of arguments in the new order!!!

Let's NOT change that now, but consider using default values for your own constructors.




Improvements

Note that when adding a buddy, we can do much more than just add the id!



We can add the whole Fruit_Buddy reference as a buddy!

Activity 3 [5 minutes]:

Go into the Fruit-Buddy Project in Replit.
Then, make the following changes:

  def add_buddy(self,other):
    # adding the other's id (this can be made better!)
    self.buddies.append(other) #<-- updated here 




And here:

  def print_buddies(self):
    print("Buddies of {} :".format(self.name), end = " ")
    for bud in self.buddies:
      print(bud.name, end=" ") #<-- updated here 
    if len(self.buddies) == 1:
      if self.buddies[0] == self.id:
        print ("** Sad Trombone **")
    print() 




We ca also now use lists of Fruit_Buddy objects!

def main():

  # the constructor can be improved with default vals
  FB_users = []

  FB_users.append( Fruit_Buddy(101, "Alice", "Apples",[]) )
  FB_users.append( Fruit_Buddy(102, "Bob", "Bananas",[]) )
  FB_users.append( Fruit_Buddy(103, "Cathy", "Clementines",[]) )
  FB_users.append( Fruit_Buddy(104, "Dan", "Dates",[]) )

  # first way to add buddies
  FB_users[0].add_buddy_list([FB_users[0], FB_users[1], FB_users[2]])
  FB_users[1].add_buddy_list([FB_users[0], FB_users[1], FB_users[2]])
  FB_users[2].add_buddy_list([FB_users[1], FB_users[2]])
  FB_users[3].add_buddy_list([FB_users[2]])

  for bud in FB_users:
    bud.print_buddies()




After you're done making all these changes, run the code in the Scratchpad and show it to me!




Adding methods to a class

Activity 4 [2 minutes]:

We will add two mwthods to our Fruit_Buddy class:

  1. One method to remove a buddy: remove_friend
    It should receive another Fruit_Buddy as a parameter
  2. One method to remove one as someone else's buddy remove_as_friend
    It should receive another Fruit_Buddy as a parameter


(Wait; then Click)

  def remove_buddy(self,other):
    for bud in self.buddies:
      if bud == other:
        self.buddies.remove(bud)
        self.remove_as_buddy(other)

  def remove_as_buddy(self,other):
    other.remove_buddy(self)  


Why is it that this does not run forever?




Inheritance

We'll reproduce this example: With Dogs!




Polymorphism

Check out the method Speak in the example above





One last example

 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
class Character:
  def __init__(self, name="NA", player="NPC", health = 10, focus = 10):
    self.name = name
    self.player = player
    self.health = health
    self.focus = focus

  def print_char(self):
    print("{}, played by {}, has {} health and {} focus left".format(self.name, self.player, self.health, self.focus))

  def apply_damage(self,damage):
    if self.health <= damage:
      self.health = 0
      print ("AArrghhh! ... ** {} has died **".format(self.name))
    else:
      self.health -= damage  

  def apply_charm(self,charm):
    if self.focus <= charm:
      self.focus = 0
      print ("Oooohhh! ... ** {} has been charmed **".format(self.name))
    else:
      self.focus -= charm

class Warrior(Character):
  def __init__(self,name="NA", player="NPC", health = 10, focus = 10, weapon = "punch", damage = 1):
    Character.__init__(self,name, player, health, focus)
    self.weapon = weapon
    self.damage = damage

  def set_weapon (self, weapon = "punch", damage = 1):
    self.weapon = weapon
    self.damage = damage

  def  use_weapon(self,other):
    other.apply_damage(self.damage)


class Bard(Character):
  def __init__(self,name="NA", player="NPC", health = 10, focus = 10, instrument = "voice", charm = 1):
    Character.__init__(self,name, player, health, focus)
    self.instrument = instrument
    self.charm = charm

  def set_instrument (self, instrument = "voice", charm = 1):
    self.instrument = instrument
    self.charm = charm  

  def  use_instrument(self,other):
    other.apply_charm(self.charm)


def main():
  c1 = Warrior("Orkhina", "Mariana", 20, 5)
  c1.set_weapon("Axe",5)
  c2 = Bard("Elfrank", "Pablo", 6, 20)
  c2.set_instrument("Flute", 10)

  c1.use_weapon(c2)
  c2.print_char()

  c2.use_instrument(c1)
  c1.print_char()

if __name__ == "__main__":
  main()




Additional readings

Check out this article on Inheritance

Also this one with a Polymorphism




Homework

[Due for everyone]

Check out this file : graphics.py
And their documentation graphics.pdf

Assignment 07 is Due 04/01 ( with extension to 04/04 by 5PM)

[Optional]