Rao Discussion 7: OOP, String Representation
This discussion worksheet is for the Rao offering of CS 61A. Your work is not graded and you do not need to submit anything.
OOP
Consult the drop-down if you need a refresher on Object-Oriented Programming. It's okay to skip directly to the questions and refer back here should you get stuck.Object-oriented programming (OOP) is a programming paradigm that allows us to treat data as objects, like we do in real life.
For example, consider the class Student
.
Each of you as individuals is an instance of this class.
Details that all CS 61A students have, such as name
,
are called instance variables.
Every student has these variables, but their values differ from student to student.
A variable that is shared among all instances of Student
is known as a class variable.
For example, the extension_days
attribute is a class variable
as it is a property of all students.
All students are able to do homework, attend lecture, and go to office hours.
When functions belong to a specific object, they are called methods.
In this case, these actions would be methods of Student
objects.
Here is a recap of what we discussed above:
- class: a template for creating objects
- instance: a single object created from a class
- instance variable: a data attribute of an object, specific to an instance
- class variable: a data attribute of an object, shared by all instances of a class
- method: a bound function that may be called on all instances of a class
Instance variables, class variables, and methods are all considered attributes of an object.
Q1: WWPD: Legally Blonde OOP
Below we have defined the classes Student
and Professor
.
Remember that Python passes the self
argument implicitly to methods when calling the method directly on an object.
class Student:
extension_days = 3 # this is a class variable
def __init__(self, name, staff):
self.name = name # this is an instance variable
self.understanding = 0
staff.add_student(self)
print("Added", self.name)
def visit_office_hours(self, staff):
staff.assist(self)
print("Thanks, " + staff.name)
class Professor:
def __init__(self, name):
self.name = name
self.students = {}
def add_student(self, student):
self.students[student.name] = student
def assist(self, student):
student.understanding += 1
def grant_more_extension_days(self, student, days):
student.extension_days = days
What will the following lines output?
>>> callahan = Professor("Callahan")
>>> elle = Student("Elle", callahan)
>>> elle.visit_office_hours(callahan)
>>> elle.visit_office_hours(Professor("Paulette"))
>>> elle.understanding
>>> [name for name in callahan.students]
>>> x = Student("Vivian", Professor("Stromwell")).name
>>> x
>>> elle.extension_days
>>> callahan.grant_more_extension_days(elle, 7)
>>> elle.extension_days
>>> Student.extension_days
Q2: Email
We would like to write three different classes (Server
, Client
,
and Email
) to simulate a system for sending and receiving emails. A Server
has a dictionary mapping client names to Client
objects, and can both send
Email
s to Client
s in the Server
and register new Client
s. A Client
can both compose emails (which first creates a new Email
object and then
sends it to the recipient client through the server) and receive an email
(which places an email into the client's inbox).
Emails will only be sent/received within the same server, so clients will always use the server they're registered in to send emails to other clients that are registered in the same rerver.
An example flow:
A Client
object (Client 1) composes an Email
object with message "hello"
with recipient Client 2,
which the Server
routes to Client 2's inbox.
Q3: Keyboard
Below is the definition of a Button
class, which represents a button
on a keyboard. It has three attributes: pos
(numerical position
of the button on the keyboard), key
(the letter of the button), and
times_pressed
(the number of times the button is pressed).
class Button:
def __init__(self, pos, key):
self.pos = pos
self.key = key
self.times_pressed = 0
We'd like to create a Keyboard
class that takes in an arbitrary
number of Button
s and stores these Button
s in a dictionary. The
keys in the dictionary will be int
s that represent the position on the
Keyboard
, and the values will be the respective Button
. Fill out
the methods in the Keyboard
class according to each description.
Important: Utilize the doctests as a reference for the behavior of a Keyboard
instance.
- Hint: You can iterate over
*args
as if it were a list.
Inheritance
Consult the drop-down if you need a refresher on Inheritance. It's okay to skip directly to the questions and refer back here should you get stuck.To avoid redefining attributes and methods for similar classes,
we can write a single base class from which the similar classes inherit.
For example, we can write a class called Pet
and define Dog
as a subclass of Pet
:
class Pet:
def __init__(self, name, owner):
self.is_alive = True # It's alive!!!
self.name = name
self.owner = owner
def eat(self, thing):
print(self.name + " ate a " + str(thing) + "!")
def talk(self):
print(self.name)
class Dog(Pet):
def talk(self):
super().talk()
print('This Dog says woof!')
Inheritance represents a hierarchical relationship between two or more
classes where one class is a more specific version of the other:
a dog is a pet
(We use is a to describe this sort of relationship in OOP languages,
and not to refer to the Python is
operator).
Since Dog
inherits from Pet
, the Dog
class will also inherit the
Pet
class's methods, so we don't have to redefine __init__
or eat
.
We do want each Dog
to talk
in a Dog
-specific way,
so we can override the talk
method.
We can use super()
to refer to the superclass of self
,
and access any superclass methods as if we were an instance of the superclass.
For example, super().talk()
in the Dog
class will call the talk()
method from the Pet
class, but passing the Dog
instance as the self
.
This is a little bit of a simplification,
and if you're interested you can read more
in the
Python documentation
on super
.
Q4: That's inheritance, init?
Let's say we want to create a class Monarch
that inherits from another class, Butterfly
. We've partially written an __init__
method for Monarch
. For each of the following options, state whether it would correctly complete the method so that every instance of Monarch
has all of the instance attributes of a Butterfly
instance. You may assume that a monarch butterfly has the default value of 2 wings.
super.__init__()
super().__init__()
Butterfly.__init__()
Butterfly.__init__(self)
Some butterflies like the Owl Butterfly have adaptations that allow them to mimic other animals with their wing patterns. Let's write a class for these MimicButterflies
. In addition to all of the instance variables of a regular Butterfly
instance, these should also have an instance variable mimic_animal
describing the name of the animal they mimic. Fill in the blanks in the lines below to create this class.
Q5: Cat
Below is the implementation of a Pet
class. Each pet has three instance attributes
(is_alive
, name
, and owner
), as well as two class methods (eat
and talk
).
class Pet():
def __init__(self, name, owner):
self.is_alive = True # It's alive!!!
self.name = name
self.owner = owner
def eat(self, thing):
print(self.name + " ate a " + str(thing) + "!")
def talk(self):
print(self.name)
Implement the Cat
class, which inherits from
the Pet
class seen above. To complete the implementation, override the
__init__
and talk
methods and add a new lose_life
method.
Run in 61A CodeHint: You can call the
__init__
method ofPet
(the superclass ofCat
) to set a cat'sname
andowner
.Hint: The
__init__
method can be called at any point and used just like any other method.
Q6: NoisyCat
More cats! Fill in this implementation of a class called
NoisyCat
, which is just like a normal Cat
. However,
NoisyCat
talks a lot: in fact, it talks twice as much as a regular Cat
!
If you'd like to test your code, feel free to copy over your solution to the Cat
class above.
Representation: Repr, Str
Consult the drop-down if you need a refresher onrepr
and str
. It's
okay to skip directly to the questions and refer back
here should you get stuck.
There are two main ways to produce the "string" of an object in Python:
str()
and repr()
. While the two are similar, they are
used for different purposes.
str()
is used to describe
the object to the end user in a "Human-readable" form,
while repr()
can be thought of as a "Computer-readable"
form mainly used for debugging and development.
When we define a class in Python, __str__
and __repr__
are
both built-in methods for the class.
We can call those methods using the
global built-in functions str(obj)
or repr(obj)
instead of
dot notation,
obj.__repr__()
or obj.__str__()
.
In addition, the print()
function calls the __str__
method of the object
and displays the returned string with the quotations removed,
while simply calling the object in interactive mode in the interpreter calls the _repr__
method
and displays the returned string with the quotations removed.
Here are some examples:
class Rational:
def __init__(self, numerator, denominator):
self.numerator = numerator
self.denominator = denominator
def __str__(self):
return str(self.numerator) + '/' + str(self.denominator)
def __repr__(self):
return 'Rational' + '(' + str(self.numerator) + ',' + str(self.denominator) + ')'
>>> a = Rational(1, 2)
>>> [str(a), repr(a)]
['1/2', 'Rational(1,2)']
>>> print(a)
1/2
>>> a
Rational(1,2)
Q7: WWPD: Repr-esentation
Note: This is not the typical way
repr
is used, nor is this way of writingrepr
recommended, this problem is mainly just to make sure you understand howrepr
andstr
work.
class Car:
def __init__(self, color):
self.color = color
def __repr__(self):
return self.color
def __str__(self):
return self.color * 2
class Garage:
def __init__(self):
print('Vroom!')
self.cars = []
def add_car(self, car):
self.cars.append(car)
def __repr__(self):
print(len(self.cars))
ret = ''
for car in self.cars:
ret += str(car)
return ret
Given the above class definitions, what will the following lines output?
>>> Car('red')
>>> print(Car('red'))
>>> repr(Car('blue'))
>>> g = Garage()
>>> g.add_car(Car('red'))
>>> g.add_car(Car('blue'))
>>> g
Q8: Cat Representation
Now let's implement the __str__
and __repr__
methods for the Cat
class from earlier so that they exhibit the following behavior:
>>> cat = Cat("Felix", "Kevin")
>>> cat
Felix, 9 lives
>>> cat.lose_life()
>>> cat
Felix, 8 lives
>>> print(cat)
Felix
Run in 61A Code