27. Simple OOP with classes — Homework solutions
The .py solution files are in
exercises/27/homework/solutions/.
Problem 1 — Point class with move
Problem. A Point class with
__init__, distance, and move.
How to think about it. Start from the chapter's
Point class. Add one method, move(dx, dy),
that mutates self.x and self.y.
Worked solution.
import math
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def distance(self, other):
dx = self.x - other.x
dy = self.y - other.y
return math.sqrt(dx * dx + dy * dy)
def move(self, dx, dy):
self.x = self.x + dx
self.y = self.y + dy
a = Point(0, 0)
b = Point(3, 4)
print(a.distance(b)) # 5.0
a.move(1, 1)
print(a.x, a.y) # 1 1
print(a.distance(b)) # sqrt(2^2 + 3^2) ~ 3.6055...Common mistakes.
- Forgetting
selfonmoveanddistance. Python will complain that the method receives too many arguments. - Assigning
x = xinstead ofself.x = xin__init__. The field would not be stored on the instance at all.
Problem 2 — Character class
Worked solution.
class Character:
def __init__(self, name, hp):
self.name = name
self.hp = hp
self.max_hp = hp
def take_damage(self, amount):
self.hp = self.hp - amount
if self.hp < 0:
self.hp = 0
def heal(self, amount):
self.hp = self.hp + amount
if self.hp > self.max_hp:
self.hp = self.max_hp
def is_alive(self):
return self.hp > 0
def report(self):
print(f"{self.name}: {self.hp} / {self.max_hp} HP (alive: {self.is_alive()})")
c = Character("Keiko", 100)
c.report()
c.take_damage(30)
c.report()
c.take_damage(80)
c.report()
c.heal(20)
c.report()A sample run:
Keiko: 100 / 100 HP (alive: True)
Keiko: 70 / 100 HP (alive: True)
Keiko: 0 / 100 HP (alive: False)
Keiko: 20 / 100 HP (alive: True)
report is a good place for an f-string — three values in
a fixed template line.
Common mistakes.
- Letting
hpgo negative or abovemax_hp. The twoifchecks intake_damageandhealclamp the values.
Problem 3 — Rectangle class
Worked solution.
class Rectangle:
def __init__(self, w, h):
self.w = w
self.h = h
def area(self):
return self.w * self.h
def perimeter(self):
return 2 * (self.w + self.h)
r1 = Rectangle(3, 4)
r2 = Rectangle(10, 2)
print(r1.area()) # 12
print(r1.perimeter()) # 14
print(r2.area()) # 20
print(r2.perimeter()) # 24Common mistakes.
- Storing
widthandheightas method-level locals instead ofself.wandself.h. An object exists so its fields stay with the instance between method calls.
Challenge — Animal, Dog, Cat
Worked solution.
# Base class
class Animal:
def __init__(self, name):
self.name = name
def describe(self):
print(f"I am {self.name}.")
# Dog inherits Animal
class Dog(Animal):
def __init__(self, name):
super().__init__(name)
def bark(self):
print(f"{self.name}: Woof!")
# Cat inherits Animal
class Cat(Animal):
def __init__(self, name):
super().__init__(name)
def meow(self):
print(f"{self.name}: Meow.")
rex = Dog("Rex")
whiskers = Cat("Whiskers")
rex.describe() # I am Rex.
whiskers.describe() # I am Whiskers.
rex.bark() # Rex: Woof!
whiskers.meow() # Whiskers: Meow.Dog and Cat both call
super().__init__(name) so that Animal's
constructor runs and sets up self.name. Each child adds
only what is unique to it.
Common mistakes.
- Omitting
super().__init__(name)in a child's__init__. Thenself.nameis never set, anddescribe()raises anAttributeError. - Defining
__init__in the child but forgetting to callsuper(). The child constructor runs, but the parent's field setup does not.
Done?
Class-based objects (Point, Character,
Rectangle) and a small inheritance chain
(Animal -> Dog/Cat) are now in
your toolkit. The next three chapters build on this: Many
objects together manages collections of instances,
Inheritance in depth specialises one class from
another, and Designing a small class makes a class
pleasant to use.