家庭作业 4:面向对象编程、继承与可变树
截止时间:7 月 22 日(星期三)晚上 11:59
须知
下载 hw04.zip。压缩包内含
hw04.py文件以及 ok 自动评分器。
提交: 完成后,请将作业提交到 Gradescope。截止时间前可以多次提交,只有最后一次提交会被评分。请确认代码已成功提交;具体步骤参见 Lab 0 ,其中有更详细的作业提交说明。
使用 Ok: 如果对 Ok 的使用有任何疑问,请参阅 这份指南。
阅读材料: 以下参考资料可能对您有所帮助:
评分: 家庭作业按答案的正确性评分。每答错一道题,总分会扣 1 分。 本次家庭作业满分为 2 分。
必做题
如果需要复习本次作业涉及的知识,可以展开下面的内容。也可以直接开始做题,遇到困难时再回来查阅。
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
类:对象的类型。上面展示的 Car 类描述了所有 Car
对象上调用。
对象:类的一个具体实例。在 Python 中,调用类即可创建新对象。
>>> ferrari = Car('red')
这里, ferrari 是绑定到一个 Car 对象的名称。
类属性:属于类的变量,通过点号访问。 Car 类具有 max_tires 属性。
>>> Car.max_tires
4
实例属性:属于某个具体对象的变量。每个 Car 对象都有 tires 属性和 color 属性。实例属性与类属性一样通过点号访问。
>>> ferrari.color
'red'
>>> ferrari.tires
4
>>> ferrari.color = 'green'
>>> ferrari.color
'green'
方法:属于对象并通过点号调用的函数。按照约定,方法的第一个参数是 self中的一个问题。
调用对象的方法时,该对象会被隐式地作为 self的实参传入。例如,调用 drive 对象的 ferrari 方法时只需空括号,因为 self 会隐式绑定到 ferrari 对象的名称。
>>> ferrari = Car('red')
>>> ferrari.drive()
'red car goes vroom!'
也可以直接调用原始的 Car.drive 函数。原始函数不属于任何特定 Car 对象,因此必须显式提供 self中的一个问题。
>>> ferrari = Car('red')
>>> Car.drive(ferrari)
'red car goes vroom!'
__init__:创建类的新实例时自动调用的特殊函数。
请注意, drive 方法接收参数 self ,但看起来我们并没有传入它。这是因为点号会
隐式地 把 ferrari 作为 self 传入。因此在这个例子中, self 绑定到全局帧中名为 ferrari 的对象。
为了对表达式 Car('red')求值,Python 会创建新的 Car 对象,然后调用 __init__ 类的 Car 函数,并把 self 绑定到新对象,把 color 绑定到 'red'中的一个问题。
树
回顾树抽象数据类型:一棵树由标签和若干分支组成。此前我们用 Python 列表实现了树抽象;下面看看如何改用对象实现。
class Tree:
def __init__(self, label, branches=[]):
for b in branches:
assert isinstance(b, Tree)
self.label = label
self.branches = branches
def is_leaf(self):
return not self.branches
采用这种实现后,可以通过给属性赋值来改变树;先前的列表实现无法这样做。因此,这些对象有时也称为“可变树”。
>>> t = Tree(3, [Tree(4), Tree(5)])
>>> t.label = 5
>>> t.label
5
为了避免在相似的类中重复定义属性和方法,可以先编写一个 基类 ,让更具体的类从它 inherit。例如,可以编写名为 Pet 的类,再定义其 Dog as a 子类 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!')
继承表示两个或更多类之间的层次关系,其中一个类是另一个类的 is a 更具体版本:狗 is a 宠物。(在面向对象语言中,我们用“is a”描述这种关系,这里不是指 Python 的 is 运算符。)
Since Dog 继承自 Pet, the Dog 类也会继承
Pet 类的方法,因此无需重新定义 __init__ 或 eat。不过,我们希望每种 Dog 的根标签改为 talk in a Dog都有自己的特定行为,因此可以 重写 the talk 方法的返回值决定。
可以使用 super() 引用它的超类 self,并像超类实例一样访问超类方法。例如, super().talk() in the Dog 类会调用 talk
类中的方法 Pet ,但传入的是 Dog 实例作为 self中的一个问题。
面向对象编程
问题 1:自动售货机
本题将创建一台 自动售货机 ,它只销售一种商品,并在需要时找零。
实现 VendingMachine 类模拟销售某种特定商品的自动售货机。 VendingMachine 对象的方法返回描述机器状态和操作的字符串。请确保输出 exactly 与 doctest 中给出的字符串完全一致,包括标点和空格。
Python 的格式化字符串字面量(即 f-string )可能会很有用。下面是一个简单示例:
>>> feeling = 'love' >>> course = 'CS 61A!' >>> combined_string = f'I {feeling} {course}' >>> combined_string 'I love CS 61A!'
class VendingMachine:
"""A vending machine that vends some product for some price.
>>> v = VendingMachine('candy', 10)
>>> v.vend()
'Nothing left to vend. Please restock.'
>>> v.add_funds(15)
'Nothing left to vend. Please restock. Here is your $15.'
>>> v.restock(2)
'Current candy stock: 2'
>>> v.vend()
'Please add $10 more funds.'
>>> v.add_funds(7)
'Current balance: $7'
>>> v.vend()
'Please 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)
'Nothing left to vend. Please restock. 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.'
"""
def __init__(self, product: str, price: int):
"""Set the product and its price, as well as other instance attributes."""
"*** YOUR CODE HERE ***"
def restock(self, n: int) -> str:
"""Add n to the stock and return a message about the updated stock level.
E.g., Current candy stock: 3
"""
"*** YOUR CODE HERE ***"
def add_funds(self, n: int) -> str:
"""If the machine is out of stock, return a message informing the user to restock
(and return their n dollars).
E.g., Nothing left to vend. Please restock. Here is your $4.
Otherwise, add n to the balance and return a message about the updated balance.
E.g., Current balance: $4
"""
"*** YOUR CODE HERE ***"
def vend(self) -> str:
"""Dispense the product if there is sufficient stock and funds and
return a message. Update the stock and balance accordingly.
E.g., Here is your candy and $2 change.
If not, return a message suggesting how to correct the problem.
E.g., Nothing left to vend. Please restock.
Please add $3 more funds.
"""
"*** YOUR CODE HERE ***"
使用 Ok 测试你的代码:
python3 ok -q VendingMachine
可变树
问题 2:累积乘积
编写函数 cumulative_mul ,它改变树 t ,把每个节点的标签替换为该标签与其所有后代节点标签的乘积。
提示:请留意修改当前节点标签与处理子树的先后顺序;哪一步应当先做?
def cumulative_mul(t: Tree) -> None:
"""Mutates t so that each node's label becomes the product of its own
label and all labels in the corresponding subtree rooted at t.
>>> t = Tree(1, [Tree(3, [Tree(5)]), Tree(7)])
>>> cumulative_mul(t)
>>> t
Tree(105, [Tree(15, [Tree(5)]), Tree(7)])
>>> otherTree = Tree(2, [Tree(1, [Tree(3), Tree(4), Tree(5)]), Tree(6, [Tree(7)])])
>>> cumulative_mul(otherTree)
>>> otherTree
Tree(5040, [Tree(60, [Tree(3), Tree(4), Tree(5)]), Tree(42, [Tree(7)])])
"""
"*** YOUR CODE HERE ***"
使用 Ok 测试你的代码:
python3 ok -q cumulative_mul
问题 3:修剪较小分支
从树中移除部分节点称为 pruning 这棵树。
补全函数 prune_small ,它接收一棵树 Tree t 以及一个数 n。对于分支数超过 n 的每个节点,只保留标签最小的 n 个分支,并移除(prune)其余分支。
提示:
max函数接收一个可迭代对象iterable以及可选的key参数(接收一个单参数函数)。例如,max([-7, 2, -1], key=abs)会返回-7sinceabs(-7)大于abs(2)和abs(-1)中的一个问题。这个
remove函数接收一个值value,改变列表并移除其中第一次出现的该值value。例如,如果l = [1, 2, 3, 2],调用l.remove(2)会把列表改变为lto be[1, 3, 2],因为它移除了遇到的第一个2。
def prune_small(t: Tree, n: int) -> None:
"""Prune the tree mutatively, keeping only the n branches
of each node with the smallest labels.
>>> t1 = Tree(6)
>>> prune_small(t1, 2)
>>> t1
Tree(6)
>>> t2 = Tree(6, [Tree(3), Tree(4)])
>>> prune_small(t2, 1)
>>> t2
Tree(6, [Tree(3)])
>>> t3 = Tree(6, [Tree(1), Tree(3, [Tree(1), Tree(2), Tree(3)]), Tree(5, [Tree(3), Tree(4)])])
>>> prune_small(t3, 2)
>>> t3
Tree(6, [Tree(1), Tree(3, [Tree(1), Tree(2)])])
"""
while ____:
largest = max(____, key=____)
____
for b in t.branches:
____
使用 Ok 测试你的代码:
python3 ok -q prune_small
继承
Q4: Cat
下面给出了 Pet 类的实现。每只宠物有两个实例属性(name 和 owner),还有一个实例方法(talk).
class Pet:
def __init__(self, name, owner):
self.name = name
self.owner = owner
def talk(self):
print(self.name)
实现 Cat 类,它继承自上面的 Pet 类。
class Pet:
def __init__(self, name: str, owner: str) -> None:
self.name = name
self.owner = owner
def talk(self) -> None:
print(self.name)
class Cat(Pet):
"""
>>> my_cat = Cat("Furball", "Me", lives=2)
>>> my_cat.talk()
Meow!
>>> my_cat.name
'Furball'
>>> my_cat.lose_life()
>>> my_cat.is_alive
True
>>> my_cat.eat("poison")
Meow!
Furball ate a poison!
>>> my_cat.is_alive
False
>>> my_cat.lose_life()
'Cat is dead x_x'
"""
def __init__(self, name: str, owner: str, lives: int = 9) -> None:
assert type(lives) == int and lives > 0
"*** YOUR CODE HERE ***"
def talk(self) -> None:
"""A cat says 'Meow!' when asked to talk."""
"*** YOUR CODE HERE ***"
def lose_life(self) -> str | None:
"""A cat can only lose a life if it has at least one
life. When there are zero lives left, the 'is_alive'
variable becomes False.
"""
"*** YOUR CODE HERE ***"
def eat(self, thing: str) -> None:
self.talk()
print(f"{self.name} ate a {thing}!")
if thing == "poison":
self.is_alive = False
使用 Ok 测试你的代码:
python3 ok -q Cat
在本地检查得分
你可以运行以下命令,在本地检查本次作业每道题的得分:
python3 ok --score
这不会提交作业! 确认得分符合预期后,请将作业提交到 Gradescope,以获得作业学分。
提交作业
请上传所有你修改过的文件,将本次作业提交到 对应的 Gradescope 作业入口。 实验 00 中提供了详细说明。
考试练习
家庭作业还会附上往年考试难度的题目供你练习。这些题目无需提交;如果想挑战自己,可以自由尝试!
- 2022 春季期中考试 2 问题 8: CS61A 呈现:Hoop 游戏。
- 2024 春季期末考试问题 5: 树
- 2026 春季期中考试问题 5: 结交笔友!