Discussion 6: OOP
Object-Oriented Programming
Before starting the discussion, you can read over the introduction/refresher for OOP below!
class Car:
max_tires = 4
def __init__(self, color):
self.tires = Car.max_tires
self.color = color
def drive(self):
if self.tires < Car.max_tires:
return self.color + ' car cannot drive!'
return self.color + ' car goes vroom!'
def pop_tire(self):
if self.tires > 0:
self.tires -= 1
Class: The type of an object. The Car
class (shown above) describes the characteristics of all Car
objects.
Object: A single instance of a class. In Python, a new object is created by calling a class.
>>> ferrari = Car('red')
Here, ferrari
is a name bound to a Car
object.
Class attribute: A variable that belongs to a class and is accessed via dot notation. The Car
class has a max_tires
attribute.
>>> Car.max_tires
4
Instance attribute: A variable that belongs to a particular object. Each Car
object has a tires
attribute and a color
attribute. Like class attributes, instance attributes are accessed via dot notation.
>>> ferrari.color
'red'
>>> ferrari.tires
4
>>> ferrari.color = 'green'
>>> ferrari.color
'green'
Method: A function that belongs to an object and is called via dot notation. By convention, the first parameter of a method is self
.
When one of an object's methods is called, the object is implicitly provided as the argument for self
. For example, the drive
method of the ferrari
object is called with empty parentheses because self
is implicitly bound to the ferrari
object.
>>> ferrari = Car('red')
>>> ferrari.drive()
'red car goes vroom!'
We can also call the original Car.drive
function. The original function does not belong to any particular Car
object, so we must provide an explicit argument for self
.
>>> ferrari = Car('red')
>>> Car.drive(ferrari)
'red car goes vroom!'
__init__: A special function that is called automatically when a new instance of a class is created.
Notice how the drive
method takes in self
as an argument, but it
looks like we didn't pass one in! This is because the dot notation
implicitly passes in ferrari
as self
for us. So in this example, self
is bound to the
object called ferrari
in the global frame.
To evaluate the expression Car('red')
, Python creates a new Car
object. Then, Python calls the __init__
function of the Car
class with self
bound to the new object and color
bound to 'red'
.
Dog
and Cat
classes:
class Dog:
def __init__(self, name, owner):
self.is_alive = True
self.name = name
self.owner = owner
def eat(self, thing):
print(self.name + " ate a " + str(thing) + "!")
def talk(self):
print(self.name + " says woof!")
class Cat:
def __init__(self, name, owner, lives=9):
self.is_alive = True
self.name = name
self.owner = owner
self.lives = lives
def eat(self, thing):
print(self.name + " ate a " + str(thing) + "!")
def talk(self):
print(self.name + " says meow!")
Dogs and cats have a lot in common, so there is a lot of repeated code! To avoid redefining shared attributes and methods, we can write a single base class that is inherited by more specific classes.
For example, we can write a Pet
class that serves as the base class for Dog
:
class Pet:
def __init__(self, name, owner):
self.is_alive = True
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):
print(self.name + ' says woof!')
Inheritance lets us indicate that one class is a more specific version of another. Each dog is a pet; in other words, Dog
is a subclass of Pet
, and Pet
is a superclass of Dog
.
We don't have to redefine __init__
or eat
because Dog
inherits those functions from Pet
. However, we want each dog to talk in a way that is unique to dogs, so we have the Dog
class override the talk
function.
We can also rewrite the Cat
class as a subclass of Pet
:
class Cat(Pet):
def __init__(self, name, owner, lives=9):
super().__init__(name, owner)
self.lives = lives
def talk(self):
print(self.name + " says meow!")
The super()
expression lets us access the functions of a superclass (e.g. Pet
) within the functions of a subclass (e.g. Cat
).
In the __init__
function of the Cat
class, super()
evaluates to a version of self
that behaves as if it were a Pet
instead of a Cat
.
The expression super().__init__(name, owner)
is equivalent to Pet.__init__(self, name, owner)
. By calling super().__init__
, we avoid rewriting the code that assigns is_alive
, name
, and owner
attributes.
Hint: A good way to get started with defining a class is to determine the necessary class attributes and instance attributes. Before implementing a class, describe the type of each attribute and how it will be used.
Q1: Keyboard
Overview: A keyboard has a button for every letter of the alphabet. When a
button is pressed, it outputs its letter by calling an output
function (such
as print
). Whether that letter is uppercase or lowercase depends on how many
times the caps lock key has been pressed.
First, implement the Button
class, which takes a lowercase letter
(a
string) and a one-argument output
function, such as Button('c', print)
.
The press
method of a Button
calls its output
attribute (a function) on
its letter
attribute: either uppercase if caps_lock
has been pressed an odd
number of times or lowercase otherwise. The press
method also increments
pressed
and returns the key that was pressed. Hint: 'hi'.upper()
evaluates
to 'HI'
.
Second, implement the Keyboard
class. A Keyboard
has a dictionary called
keys
containing a Button
(with its letter
as its key) for each letter in
LOWERCASE_LETTERS
. It also has a list of the letters typed
, which may be
a mix of uppercase and lowercase letters.
The type
method takes a string word
containing only lowercase letters. It
invokes the press
method of the Button
in keys
for each letter in word
,
which adds a letter (either lowercase or uppercase depending on caps_lock
) to
the Keyboard
's typed
list. Important: Do not use upper
or letter
in
your implementation of type
; just call press
instead.
Read the doctests and talk about:
- Why it's possible to press a button repeatedly with
.press().press().press()
. - Why pressing a button repeatedly sometimes prints on only one line and sometimes prints multiple lines.
- Why
bored.typed
has 10 elements at the end.
Discussion Time: Before anyone types anything, have a conversation
describing the type of each attribute and how it will be used. Start with
Button
: how will letter
and output
be used? Then discuss Keyboard
: how
will typed
and keys
be used? How will new letters be added to the list
called typed
each time a Button
in keys
is pressed? Call the staff if
you're not sure! Once everyone understands the answers to these questions, you
can try writing the code together.
self.letter
is always lowercase, use self.letter.upper()
to produce the uppercase version.
Presentation Time: Describe how new letters are added to
typed
each time a Button
in keys
is pressed. Instead of just reading
your code, say what it does (e.g., "When the button of a keyboard is pressed ...").
One short sentence is enough to describe how new letters are added to typed
.
Q2: Shapes
Fill out the skeleton below for a set of classes used to describe geometric shapes. Each class has an area
and a perimeter
method, but the implementation of those methods is slightly different. Please override the base Shape
class's methods where necessary so that we can accurately calculate the perimeters and areas of our shapes with ease.
Q3: Bear
Implement the SleepyBear
, and WinkingBear
classes so that calling their print
method
matches the doctests. Use as little code as possible and try not to
repeat any logic from Eye
or Bear
. Each blank can be filled with just two
short lines.
Discussion Time: Before writing code, talk about what is different about a
SleepyBear
and a Bear
. When using inheritance, you only need to implement
the differences between the base class and subclass. Then, talk about what is
different about a WinkingBear
and a Bear
. Can you think of a way to make
the bear wink without a new implementation of print
?
class Eye:
"""An eye.
>>> Eye().draw()
'0'
>>> print(Eye(False).draw(), Eye(True).draw())
0 -
"""
def __init__(self, closed=False):
self.closed = closed
def draw(self):
if self.closed:
return '-'
else:
return '0'
class Bear:
"""A bear.
>>> Bear().print()
? 0o0?
"""
def __init__(self):
self.nose_and_mouth = 'o'
def next_eye(self):
return Eye()
def print(self):
left, right = self.next_eye(), self.next_eye()
print('? ' + left.draw() + self.nose_and_mouth + right.draw() + '?')
Submit Attendance
You're done! Excellent work this week. Please be sure to ask your section TA for the attendance form link and fill it out for credit. (one submission per person per section).