Discussion 7: Object-Oriented Programming
OOP
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: Student OOP
Below we have defined the classes Professor
and Student
, implementing some of what was described above.
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
>>> [name for name in callahan.students]
>>> 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.
To solve this problem, we'll split the section into two halves (students on the left and students on the right):
- Everyone will implement the
Email
class together - The first half (left) will implement the
Server
class - The other half (right) will implement the
Client
class
Fill in the definitions below to finish the implementation!
Run in 61A CodeQ3: Keyboard
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,
using the doctests as a reference for the behavior of a Keyboard
.
Run in 61A CodeHint: You can iterate over *args as if it were a list.
Q4: Relay
In a Math Olympiad style relay, team members solve questions while sitting in a line. Each team member's answer is calculated based on the answer from the team member sitting in front of them.
For example, suppose we have three team members, adder
, adder2
, and multiplier
, with adder
sitting at the very front, adder2
in the middle, and multiplier
at the end. When we call the relay_calculate
method from multiplier
, we first apply the adder
operation to the input x
. Then, the answer from adder
is passed into the adder2
operation. Finally, the answer from adder2
is passed into the multiplier
operation. The answer from multiplier
is our final answer.
Additionally, each team member has a relay_history
method, which uses the fact that each team member has an instance variable history
. relay_history
returns a list of the answers given by each team member, and this is updated each time we call relay_calculate
.
Here are some examples of how the TeamMember
class should behave:
>>> adder = TeamMember(lambda x: x + 1) # team member at front
>>> adder2 = TeamMember(lambda x: x + 2, adder) # team member 2
>>> multiplier = TeamMember(lambda x: x * 5, adder2) # team member 3
>>> adder.relay_history() # relay history starts off as empty
[]
>>> adder.relay_calculate(5) # 5 + 1
6
>>> adder2.relay_calculate(5) # (5 + 1) + 2
8
>>> multiplier.relay_calculate(5) # (((5 + 1) + 2) * 5)
40
>>> multiplier.relay_history() # history of answers from the most recent relay multiplier participated in
[6, 8, 40]
>>> adder.relay_history()
[6]
>>> multiplier.relay_calculate(4) # (((4 + 1) + 2) * 5)
35
>>> multiplier.relay_history()
[5, 7, 35]
>>> adder.relay_history() # adder participated most recently in multiplier.relay_calculate(4), where it gave the answer 5
[5]
>>> adder.relay_calculate(1)
2
>>> adder.relay_history() # adder participated most recently in adder.relay_calculate(1), where it gave the answer 2
[2]
>>> multiplier.relay_history() # but the most relay multiplier participated in is still multiplier.relay_calculate(4)
[5, 7, 35]
Fill in the definitions below to complete the implementation of the TeamMember class!
Run in 61A CodeClass Methods
Now we'll try out another feature of Python classes: class methods.
A method can be turned into a class method by adding the
classmethod
decorator.
Then, instead of receiving the instance as the first argument (self
),
the method will receive the class itself (cls
).
Class methods are commonly used to create "factory methods": methods whose job is to construct and return a new instance of the class.
For example, we can add a robo_factory
class method to our Dog
class
that makes robo-dogs:
class Dog:
def __init__(self, name, owner):
self.name = name
self.owner = owner
@classmethod
def robo_factory(cls, owner):
return cls("RoboDog", owner)
# With other previously defined methods not written out
Then a call to Dog.robo_factory('Sally')
would return a new Dog
instance
with the name "RoboDog" and owner "Sally".
Note that with the call above, we don't have to explicitly pass in the Dog
class as the
cls
argument, since Python implicitly does that for us. We only have to pass in a value for
owner
. When the body of the Dog.robo_factory
is run, the line cls("RoboDog", owner)
is equivalent to
Dog("RoboDog", owner)
(since cls
is bound to the Dog
class), which creates the new Dog
instance.
Q5: Own A Cat
Now implement the cat_creator
method below,
which takes in a string owner
and creates a Cat
named "[owner]'s Cat", where [owner] is replaced with the name in the owner
string.
Run in 61A CodeHint: To place an apostrophe within a string, the entire string must be surrounded in double-quotes (i.e.
"DeNero's Dog"
)