Instructions
In this lab, you have two tasks:
- Think about what would Python display in section 3 and verify your answer with Ok.
- Complete the required problems described in section 4 and submit your code to our OJ website.
The starter code for these problems is provided in lab06.py.
Submission: As instructed before, you need to submit your work with Ok by python ok --submit
.
You may submit more than once before the deadline,
and your score of this assignment will be the highest one of all your submissions.
Readings: You might find the following reference to the textbook useful:
Review
Consult this section if you need a refresher on the material for this lab. It's okay to skip directly to the next section and refer back here when you get stuck.
Object-Oriented Programming
Object-oriented programming (OOP) is a style of programming that allows you to think of code in terms of "objects." Here's an example of a Car
class:
#{{}}
class Car(object):
num_wheels = 4
def __init__(self, color):
self.wheels = Car.num_wheels
self.color = color
def drive(self):
if self.wheels <= Car.num_wheels:
return self.color + ' car cannot drive!'
return self.color + ' car goes vroom!'
def pop_tire(self):
if self.wheels > 0:
self.wheels -= 1
Here's some terminology:
-
class: a blueprint for how to build a certain type of object. The
Car
class (shown above) describes the behavior and data that allCar
objects have. -
instance: a particular occurrence of a class. In Python, we create instances of a class like this:
>>> my_car = Car('red')
my_car
is an instance of theCar
class. -
attribute or field: a variable that belongs to the class. Think of an attribute as a quality of the object: cars have wheels and color, so we have given our
Car
classself.wheels
andself.color
attributes. We can access attributes using dot notation:>>> my_car.color 'red' >>> my_car.wheels 4
-
method: Methods are just like normal functions, except that they are tied to an instance or a class. Think of a method as a "verb" of the class: cars can drive and also pop their tires, so we have given our
Car
class the methodsdrive
andpop_tire
. We call methods using dot notation:>>> my_car = Car('red') >>> my_car.drive() 'red car goes vroom!'
-
constructor: As with data abstraction, constructors describe how to build an instance of the class. Most classes have a constructor. In Python, the constructor of the class defined as
__init__
. For example, here is theCar
class's constructor:def __init__(self, color): self.wheels = Car.num_wheels self.color = color
The constructor takes in one argument,
color
. As you can see, the constructor also creates theself.wheels
andself.color
attributes. -
self
: in Python,self
is the first parameter for many methods (in this class, we will only use methods whose first parameter isself
). When a method is called,self
is bound to an instance of the class. For example:>>> my_car = Car('red') >>> car.drive()
Notice that the
drive
method takes inself
as an argument, but it looks like we didn't pass one in! This is because the dot notation implicitly passes incar
asself
for us.
Inheritance
To avoid redefining attributes and methods for similar classes, we can write a single base class from which more specialized 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
.
3. What Would Python Display?
In this section, you need to think about what python would display if the code given were input to a python interpreter.
Question 1
Use Ok to test your knowledge with the following "What Would Python Display?" questions:
python ok -q q1 -u
Important: Type
Function
if you believe the answer is<function...>
,Error
if it errors, andNothing
if nothing is displayed.
Below is the definition of a Car
class that we will be using in the following WWPD questions.
class Car(object):
num_wheels = 4
gas = 30
headlights = 2
size = 'Tiny'
def __init__(self, make, model):
self.make = make
self.model = model
self.color = 'No color yet. You need to paint me.'
self.wheels = Car.num_wheels
self.gas = Car.gas
def paint(self, color):
self.color = color
return self.make + ' ' + self.model + ' is now ' + color
def drive(self):
if self.wheels < Car.num_wheels or self.gas <= 0:
return 'Cannot drive!'
self.gas -= 10
return self.make + ' ' + self.model + ' goes vroom!'
def pop_tire(self):
if self.wheels > 0:
self.wheels -= 1
def fill_gas(self):
self.gas += 20
return 'Gas level: ' + str(self.gas)
>>> deneros_car = Car('Tesla', 'Model S')
>>> deneros_car.model
______
>>> deneros_car.gas = 10
>>> deneros_car.drive()
______
>>> deneros_car.drive()
______
>>> deneros_car.fill_gas()
______
>>> deneros_car.gas
______
>>> Car.gas
______
>>> deneros_car = Car('Tesla', 'Model S')
>>> deneros_car.wheels = 2
>>> deneros_car.wheels
______
>>> Car.num_wheels
______
>>> deneros_car.drive()
______
>>> Car.drive()
______
>>> Car.drive(deneros_car)
______
For the following, we reference the MonsterTruck
class below.
class MonsterTruck(Car):
size = 'Monster'
def rev(self):
print('Vroom! This Monster Truck is huge!')
def drive(self):
self.rev()
return Car.drive(self)
>>> deneros_car = MonsterTruck('Monster', 'Batmobile')
>>> deneros_car.drive()
______
>>> Car.drive(deneros_car)
______
>>> MonsterTruck.drive(deneros_car)
______
>>> Car.rev(deneros_car)
______
Question 2
Use Ok to test your knowledge with the following "What Would Python Display?" questions:
python ok -q q2 -u
Important: Type
Function
if you believe the answer is<function...>
,Error
if it errors, andNothing
if nothing is displayed.
>>> class A:
... x, y = 0, 0
... def __init__(self):
... return
>>> class B(A):
... def __init__(self):
... return
>>> class C(A):
... def __init__(self):
... return
>>> print(A.x, B.x, C.x)
______
>>> B.x = 2
>>> print(A.x, B.x, C.x)
______
>>> A.x += 1
>>> print(A.x, B.x, C.x)
______
>>> obj = C()
>>> obj.y = 1
>>> C.y == obj.y
______
>>> A.y = obj.y
>>> print(A.y, B.y, C.y, obj.y)
______
Required Problems
In this section, you are required to complete the problems below and submit your code to OJ website as instructed in lab00 to get your answer scored.
Problem 1: Vending Machine (100pts)
Create a class called VendingMachine
that represents a vending machine for some product. A VendingMachine
object returns strings describing its interactions.
Fill in the VendingMachine
class, adding attributes and methods as appropriate, such that its behavior matches the following doctests:
class VendingMachine:
"""A vending machine that vends some product for some price.
>>> v = VendingMachine('candy', 10)
>>> v.vend()
'Machine is out of stock.'
>>> v.add_funds(15)
'Machine is out of stock. Here is your $15.'
>>> v.restock(2)
'Current candy stock: 2'
>>> v.vend()
'You must add $10 more funds.'
>>> v.add_funds(7)
'Current balance: $7'
>>> v.vend()
'You must add $3 more funds.'
>>> v.add_funds(5)
'Current balance: $12'
>>> v.vend()
'Here is your candy and $2 change.'
>>> v.add_funds(10)
'Current balance: $10'
>>> v.vend()
'Here is your candy.'
>>> v.add_funds(15)
'Machine is out of stock. Here is your $15.'
>>> w = VendingMachine('soda', 2)
>>> w.restock(3)
'Current soda stock: 3'
>>> w.restock(3)
'Current soda stock: 6'
>>> w.add_funds(2)
'Current balance: $2'
>>> w.vend()
'Here is your soda.'
"""
"*** YOUR CODE HERE ***"
You may find Python string formatting syntax or f-strings useful. A quick example:
>>> ten, twenty, thirty = 10, 'twenty', [30] >>> '{0} plus {1} is {2}'.format(ten, twenty, thirty) '10 plus twenty is [30]' >>> feeling = 'love' >>> course = 'SICP' >>> year = 2024 >>> f'I {feeling} {course}-{year}!' 'I love SICP-2024!'
Problem 2: Cat (100pts)
Below is a skeleton for the Cat
class, which inherits from the Pet
class. To complete the implementation, override the __init__
and talk
methods and add a new lose_life
method, such that its behavior matches the following doctests.
We may change the implementation of Pet
while testing your code, so make sure you use inheritance correctly.
Hint 1: You can call the
__init__
method of Pet to set a cat's name and owner.Hint 2: The
to_str
method belongs to Problem 4, you don't need to implement it for the moment.
class Pet:
"""A pet.
>>> kyubey = Pet('Kyubey', 'Incubator')
>>> kyubey.talk()
Kyubey
>>> kyubey.eat('Grief Seed')
Kyubey ate a Grief Seed!
"""
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)
def to_str(self):
"*** YOUR CODE HERE ***"
class Cat(Pet):
"""A cat.
>>> vanilla = Cat('Vanilla', 'Minazuki Kashou')
>>> isinstance(vanilla, Pet) # check if vanilla is an instance of Pet.
True
>>> vanilla.talk()
Vanilla says meow!
>>> vanilla.eat('fish')
Vanilla ate a fish!
>>> vanilla.lose_life()
>>> vanilla.lives
8
>>> vanilla.is_alive
True
>>> for i in range(8):
... vanilla.lose_life()
>>> vanilla.lives
0
>>> vanilla.is_alive
False
>>> vanilla.lose_life()
Vanilla has no more lives to lose.
"""
def __init__(self, name, owner, lives=9):
"*** YOUR CODE HERE ***"
def talk(self):
""" Print out a cat's greeting.
"""
"*** YOUR CODE HERE ***"
def lose_life(self):
"""Decrements a cat's life by 1. When lives reaches zero, 'is_alive'
becomes False. If this is called after lives has reached zero, print out
that the cat has no more lives to lose.
"""
"*** YOUR CODE HERE ***"
def to_str(self):
"*** YOUR CODE HERE ***"
Problem 3: Noisy Cat (100pts)
More cats! Fill in this implementation of a class called NoisyCat
, which is just like a normal Cat
. However, NoisyCat
talks a lot -- twice as much as a regular Cat
!
We may change the implementation of Pet
and Cat
while testing your code, so make sure you use inheritance correctly.
class NoisyCat: # Dose this line need to change?
"""A Cat that repeats things twice.
>>> chocola = NoisyCat('Chocola', 'Minazuki Kashou')
>>> isinstance(chocola, Cat) # check if chocola is an instance of Cat.
True
>>> chocola.talk()
Chocola says meow!
Chocola says meow!
"""
def __init__(self, name, owner, lives=9):
# Is this method necessary? If not, feel free to remove it.
"*** YOUR CODE HERE ***"
def talk(self):
"""Talks twice as much as a regular cat.
"""
"*** YOUR CODE HERE ***"
Problem 4: Pretty Printer (100pts)
Part 1
Your first task is to define pretty printer methods for Pet
, Cat
and NoisyCat
.
We start with implementing functions named to_str
for Pet
, Cat
, and NoisyCat
classes.
For Pet
, the output should be:
>>> kyubey = Pet('Kyubey', 'Incubator')
>>> kyubey.to_str()
'(Kyubey, Incubator)'
For Cat
and NoisyCat
, the output should be:
>>> vanilla = Cat('Vanilla', 'Minazuki Kashou')
>>> vanilla.to_str()
'(Vanilla, Minazuki Kashou, 9)'
>>> vanilla.lose_life()
>>> vanilla.to_str()
'(Vanilla, Minazuki Kashou, 8)'
Now, we could define a function pretty_print
, which accepts an object and print it prettily (i.e. with color).
To colorfully print something in terminal, this simplest way is to use ANSI escape code.
class Colors: HEADER = '\033[95m' OKBLUE = '\033[34m' OKCYAN = '\033[35m' OKGREEN = '\033[96m' WARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' >>> print(f"{Colors.OKBLUE}Hello, World{Colors.ENDC}") Hello, World # this line should be blue >>> print(f"{Colors.OKCYAN}Hello, World{Colors.ENDC}") Hello, World # this line should be red >>> print(f"{Colors.UNDERLINE}Hello, World{Colors.ENDC}") Hello, World # this line should be underlined
Windows Console may not be able to display that. Use vscode console or windows terminal instead.
For real world program, you should use a library to display colorful output. E.g. termcolor
pretty_print
should print object with form type(to_str)
. The type
is the class name of the input object, the to_str
is the return value of object.to_str()
. For example:
>>> kyubey = Pet('Kyubey', 'Incubator')
>>> pretty_print(kyubey)
Pet(Kyubey, Incubator)
We ignore the ascii escape code in examples, you can check the real output in doctest
Note:
type
could be obtained by python builtintype(obj).__name__
. E.g.type(kyubey).__name__
isPet
- The
type
part of the printed str should be displayed with color defined byColors.OKBLUE
. For most device/terminal, this color should be blue. - The
to_str
part of the printed str should be displayed with color defined byColors.OKCYAN
. For most device/terminal, this color should be like red.
Part 2
Your second task is to inject the defined pretty_print
method to all Pet
, Cat
and NoisyCat
classes. For example:
>>> kyubey = Pet('Kyubey', 'Incubator')
>>> kyubey.pp() # the same result as `pretty_print`
Pet(Kyubey, Incubator)
To smoothly support such method call, edit the declaration of the Pet
class.
Hint1: You may not need to edit more than one line of code for this task.
Hint2: You should use the provided
PrintModule
class.
Test your code using python -i lab06.py
with:
>>> kyubey = Pet('Kyubey', 'Incubator')
>>> kyubey.pp()
Pet(Kyubey, Incubator)
>>> vanilla = Cat('Vanilla', 'Minazuki Kashou')
>>> vanilla.pp()
Cat(Vanilla, Minazuki Kashou, 9)
This is an ugly implementation of mixin. You can check this page for ruby style mixin, which is clear and elegant.
Problem 5: Time (100pts)
In everyday programming, we often need to store dates. Typically, this involves storing three separate values: the year, the month, and the day. However, manually handling these values in separate variables each time can be cumbersome. To make it easier to store and display dates, we want to implement a class Time
that will help us manage these values more conveniently.
We create a class called Time
that represents a specific date. The class should have a constructor that accepts a year, month, and day. Additionally, it should include the following methods:
setyear(self, year)
: Sets the year of the time objectself
toyear
.setmonth(self, month)
: Sets the month of the time objectself
tomonth
.setday(self, day)
: Sets the day of the time objectself
today
.setformat(self, format_str)
: Accepts a stringformat_str
that includes'yy'
,'mm'
, and'dd'
. The format string specifies how the date should be displayed when theto_str
method is called. The default format string should be'yy.mm.dd'
.
The format_str
parameter in the setformat
method allows you to specify how the date should be displayed as a string. It is a string that defines the order and format in which the year (yy
), month (mm
), and day (dd
) should appear when the to_str
method is called.
yy
represents the two-digit year.mm
represents the two-digit month.dd
represents the two-digit day.
You can arrange these placeholders in any order you like and separate them with any delimiter, such as a dot (.
), slash (/
), or hyphen (-
). For example:
"yy.mm.dd"
will display the date as25.12.25
for the year 2025, month December, and day 25."dd-mm-yy"
will display the date as25-12-25
."mm/dd/yy"
will display the date as12/25/25
."yy.mm"
will display the date as25.12
.
The initial format_str
will be 'yy.mm.dd'
when a Time
object is created.
yy
, mm
, and dd
can appear multiple times or not appear at all. For example, 'yy/mm'
, 'yy.mm.dd yy/mm/dd'
, or even 'yyy'
are all valid formats (you only need to treat the first two y
as the year).
Hint1: When the parameter given to
setyear
,setmonth
, orsetday
is invalid (for example, if themonth
is 13 or themonth
is given as a string like "a"), you need to printParameter Error!
and do not perform any other operations.Hint2: To simplify the problem, you can assume that every month has 31 days.
Hint3: You can use
isinstance(x, int)
to check ifx
is an integer.Hint4: You might find some built-in string methods helpful, like
replace
,zfill
, etc.
class Time:
""" A class that can store and display the date.
>>> time = Time(2024, 11, 20)
>>> print(time.to_str())
24.11.20
>>> time.setyear(2023)
>>> time.setmonth(2)
>>> time.setday(5)
>>> time.setformat("dd-mm-yy")
>>> time.to_str()
'05-02-23'
>>> time.setformat("yy/mm/dd")
>>> time.to_str()
'23/02/05'
>>> time.setyear(-1)
Parameter Error!
>>> time.to_str()
'23/02/05'
"""
def __init__(self, year, month, day):
"""Initialize a Time object."""
"*** YOUR CODE HERE ***"
def setyear(self, year):
"""Set the year of the Time object."""
"*** YOUR CODE HERE ***"
def setmonth(self, month):
"""Set the month of the Time object."""
"*** YOUR CODE HERE ***"
def setday(self, day):
"""Set the day of the Time object."""
"*** YOUR CODE HERE ***"
def setformat(self, format):
"""Set the format of the Time object."""
"*** YOUR CODE HERE ***"
def to_str(self):
"""Return the formatted date."""
"*** YOUR CODE HERE ***"