A game where we take care of cute furry/ferocious animals:
Panda()
Lion()
Rabbit()
Vulture()
Elephant()
Food()
Let's start simple:
class Food:
def __init__(self, name, type, calories):
self.name = name
self.type = type
self.calories = calories
How would we use that class?
broccoli = Food("Broccoli Rabe", "veggies", 20)
bone_marrow = Food("Bone Marrow", "meat", 100)
class Elephant:
species_name = "African Savanna Elephant"
scientific_name = "Loxodonta africana"
calories_needed = 8000
def __init__(self, name, age=0):
self.name = name
self.age = age
self.calories_eaten = 0
self.happiness = 0
def play(self, num_hours):
self.happiness += (num_hours * 4)
print("WHEEE PLAY TIME!")
def eat(self, food):
self.calories_eaten += food.calories
print(f"Om nom nom yummy {food.name}")
if self.calories_eaten > self.calories_needed:
self.happiness -= 1
print("Ugh so full")
def interact_with(self, animal2):
self.happiness += 1
print(f"Yay happy fun time with {animal2.name}")
How would we use that class?
el1 = Elephant("Willaby", 5)
el2 = Elephant("Wallaby", 3)
el1.play(2)
el1.interact_with(el2)
class Rabbit:
species_name = "European rabbit"
scientific_name = "Oryctolagus cuniculus"
calories_needed = 200
def __init__(self, name, age=0):
self.name = name
self.age = age
self.calories_eaten = 0
self.happiness = 0
def play(self, num_hours):
self.happiness += (num_hours * 10)
print("WHEEE PLAY TIME!")
def eat(self, food):
self.calories_eaten += food.calories
print(f"Om nom nom yummy {food.name}")
if self.calories_eaten > self.calories_needed:
self.happiness -= 1
print("Ugh so full")
def interact_with(self, animal2):
self.happiness += 4
print(f"Yay happy fun time with {animal2.name}")
How would we use that class?
rabbit1 = Rabbit("Mister Wabbit", 3)
rabbit2 = Rabbit("Bugs Bunny", 2)
rabbit1.eat(broccoli)
rabbit2.interact_with(rabbit1)
Elephant | Rabbit |
---|---|
|
|
Elephant
and Rabbit
are both animals, so
they have similar attributes. Instead of repeating code, we can
inherit the code.
When multiple classes share similar attributes, you can reduce redundant code by defining a base class and then subclasses can inherit from the base class.
Tip: The base class is also known as the superclass.
The base class contains method headers common to the subclasses, and code that is used by multiple subclasses.
class Animal:
species_name = "Animal"
scientific_name = "Animalia"
play_multiplier = 2
interact_increment = 1
def __init__(self, name, age=0):
self.name = name
self.age = age
self.calories_eaten = 0
self.happiness = 0
def play(self, num_hours):
self.happiness += (num_hours * self.play_multiplier)
print("WHEEE PLAY TIME!")
def eat(self, food):
self.calories_eaten += food.calories
print(f"Om nom nom yummy {food.name}")
if self.calories_eaten > self.calories_needed:
self.happiness -= 1
print("Ugh so full")
def interact_with(self, animal2):
self.happiness += self.interact_increment
print(f"Yay happy fun time with {animal2.name}")
To declare a subclass, put parentheses after the class name and specify the base class in the parentheses:
class Panda(Animal):
Then the subclasses only need the code that's unique to them. They can redefine any aspect: class variables, method definitions, or constructor. A redefinition is called overriding.
The simplest subclass overrides nothing:
class AmorphousBlob(Animal):
pass
Subclasses can override existing class variables and assign new class variables:
class Rabbit(Animal):
species_name = "European rabbit"
scientific_name = "Oryctolagus cuniculus"
calories_needed = 200
play_multiplier = 8
interact_increment = 4
num_in_litter = 12
class Elephant(Animal):
species_name = "African Savanna Elephant"
scientific_name = "Loxodonta africana"
calories_needed = 8000
play_multiplier = 4
interact_increment = 2
num_tusks = 2
class LearnableContent:
"""A base class for specific kinds of learnable content.
All kinds have title and author attributes,
but each kind may have additional attributes.
"""
license = "Creative Commons"
def __init__(self, title, author):
self.title = title
self.author = author
# Create a Video subclass with
# license of "YouTube Standard License"
# Create an Article subclass with
# license of "CC-BY-NC-SA"
# Create a new Video instance with a title of "DNA" and an author of "Megan"
# Create a new Article instance with a title of "Water phases" and an author of "Lauren"
class LearnableContent:
"""A base class for specific kinds of learnable content.
All kinds have title and author attributes,
but each kind may have additional attributes.
"""
license = "Creative Commons"
def __init__(self, title, author):
self.title = title
self.author = author
# Create a Video subclass with license of "YouTube Standard License"
class Video(LearnableContent):
license = "YouTube Standard License"
# Create an Article subclass with license of "CC-BY-NC-SA"
class Article(LearnableContent):
license = "CC-BY-NC-SA"
# Create a new Video instance with a title of "DNA" and an author of "Megan"
dna_video = Video("DNA", "Megan")
# Create a new Article instance with a title of "Water phases" and an author of "Lauren"
water_article = Article("Water phases", "Lauren")
If a subclass overrides a method, Python will use that definition instead of the superclass definition.
class Panda(Animal):
species_name = "Giant Panda"
scientific_name = "Ailuropoda melanoleuca"
calories_needed = 6000
def interact_with(self, other):
print(f"I'm a Panda, I'm solitary, go away {other.name}!")
How would we call that method?
panda1 = Panda("Pandeybear", 6)
panda2 = Panda("Spot", 3)
panda1.interact_with(panda2)
class Character:
"""
>>> player = Character("Mario")
>>> player.name
'Mario'
>>> player.health
100
>>> player.damage(10)
>>> player.health
90
>>> player.boost(5)
>>> player.health
95
"""
def __init__(self, name):
self.name = name
self.health = 100
def damage(self, amount):
self.health -= amount
def boost(self, amount):
self.health += amount
class Boss(Character):
"""
>>> mx_boss = Boss("Mx Boss Person")
>>> mx_boss.damage(100)
>>> mx_boss.health
99
>>> mx_boss.damage(10)
>>> mx_boss.health
98
>>> mx_boss.boost(1)
>>> mx_boss.health
100
"""
def damage(self, amount):
# Bosses ignore the amount and instead
# always receive 1 unit of damage to their health
def boost(self, amount):
# Bosses always receive twice the amount
# of boost to their health
class Character:
"""
>>> player = Character("Mario")
>>> player.name
'Mario'
>>> player.health
100
>>> player.damage(10)
>>> player.health
90
>>> player.boost(5)
>>> player.health
95
"""
def __init__(self, name):
self.name = name
self.health = 100
def damage(self, amount):
self.health -= amount
def boost(self, amount):
self.health += amount
class Boss(Character):
"""
>>> mx_boss = Boss("Mx Boss Person")
>>> mx_boss.damage(100)
>>> mx_boss.health
99
>>> mx_boss.damage(10)
>>> mx_boss.health
98
>>> mx_boss.boost(1)
>>> mx_boss.health
100
"""
def damage(self, amount):
# Bosses ignore the amount and instead
# always receive 1 unit of damage to their health
self.health -= 1
def boost(self, amount):
# Bosses always receive twice the
# amount of boost to their health
self.health += amount * 2
class Clothing:
"""
>>> blue_shirt = Clothing("shirt", "blue")
>>> blue_shirt.category
'shirt'
>>> blue_shirt.color
'blue'
>>> blue_shirt.is_clean
True
>>> blue_shirt.wear()
>>> blue_shirt.is_clean
False
>>> blue_shirt.clean()
>>> blue_shirt.is_clean
True
"""
def __init__(self, category, color):
self.category = category
self.color = color
self.is_clean = True
def wear(self):
self.is_clean = False
def clean(self):
self.is_clean = True
class KidsClothing(Clothing):
"""
>>> onesie = KidsClothing("onesie", "polka dots")
>>> onesie.wear()
>>> onesie.is_clean
False
>>> onesie.clean()
>>> onesie.is_clean
False
>>> dress = KidsClothing("dress", "rainbow")
>>> dress.clean()
>>> dress.is_clean
True
>>> dress.wear()
>>> dress.is_clean
False
>>> dress.clean()
>>> dress.is_clean
False
"""
# Override the clean() method
# so that kids clothing always stays dirty!
class Clothing:
"""
>>> blue_shirt = Clothing("shirt", "blue")
>>> blue_shirt.category
'shirt'
>>> blue_shirt.color
'blue'
>>> blue_shirt.is_clean
True
>>> blue_shirt.wear()
>>> blue_shirt.is_clean
False
>>> blue_shirt.clean()
>>> blue_shirt.is_clean
True
"""
def __init__(self, category, color):
self.category = category
self.color = color
self.is_clean = True
def wear(self):
self.is_clean = False
def clean(self):
self.is_clean = True
class KidsClothing(Clothing):
"""
>>> onesie = KidsClothing("onesie", "polka dots")
>>> onesie.wear()
>>> onesie.is_clean
False
>>> onesie.clean()
>>> onesie.is_clean
False
>>> dress = KidsClothing("dress", "rainbow")
>>> dress.clean()
>>> dress.is_clean
True
>>> dress.wear()
>>> dress.is_clean
False
>>> dress.clean()
>>> dress.is_clean
False
"""
# Override the clean() method
# so that kids clothing always stays dirty!
def clean(self):
self.is_clean = self.is_clean
To refer to a superclass method, we can use super()
:
class Lion(Animal):
species_name = "Lion"
scientific_name = "Panthera"
calories_needed = 3000
def eat(self, food):
if food.type == "meat":
super().eat(food)
How would we call that method?
bones = Food("Bones", "meat")
mufasa = Lion("Mufasa", 10)
mufasa.eat(bones)
super().attribute
refers to the definition of
attribute
in the superclass of the first parameter to
the method.
def eat(self, food):
if food.type == "meat":
super().eat(food)
...is the same as:
def eat(self, food):
if food.type == "meat":
Animal.eat(self, food)
super()
is better style than
BaseClassName
, though slightly slower.
Similarly, we need to explicitly call
super().__init__()
if we want to call the
__init__
functionality of the base class.
class Elephant(Animal):
species_name = "Elephant"
scientific_name = "Loxodonta"
calories_needed = 8000
def __init__(self, name, age=0):
super().__init__(name, age)
if age < 1:
self.calories_needed = 1000
elif age < 5:
self.calories_needed = 3000
What would this display?
elly = Elephant("Ellie", 3)
elly.calories_needed # 3000
class Animal:
species_name = "Animal"
scientific_name = "Animalia"
play_multiplier = 2
interact_increment = 1
def __init__(self, name, age=0):
self.name = name
self.age = age
self.calories_eaten = 0
self.happiness = 0
def play(self, num_hours):
self.happiness += (num_hours * self.play_multiplier)
print("WHEEE PLAY TIME!")
def eat(self, food):
self.calories_eaten += food.calories
print(f"Om nom nom yummy {food.name}")
if self.calories_eaten > self.calories_needed:
self.happiness -= 1
print("Ugh so full")
def interact_with(self, animal2):
self.happiness += self.interact_increment
print(f"Yay happy fun time with {animal2.name}")
class Cat(Animal):
"""
>>> adult = Cat("Winston", 12)
>>> adult.name
'Winston'
>>> adult.age
12
>>> adult.play_multiplier
3
>>> kitty = Cat("Kurty", 0.5)
>>> kitty.name
'Kurty'
>>> kitty.age
0.5
>>> kitty.play_multiplier
6
"""
species_name = "Domestic cat"
scientific_name = "Felis silvestris catus"
calories_needed = 200
play_multiplier = 3
def __init__(self, name, age):
# Call the super class to set name and age
# If age is less than 1, set play multiplier to 6
class Animal:
species_name = "Animal"
scientific_name = "Animalia"
play_multiplier = 2
interact_increment = 1
def __init__(self, name, age=0):
self.name = name
self.age = age
self.calories_eaten = 0
self.happiness = 0
def play(self, num_hours):
self.happiness += (num_hours * self.play_multiplier)
print("WHEEE PLAY TIME!")
def eat(self, food):
self.calories_eaten += food.calories
print(f"Om nom nom yummy {food.name}")
if self.calories_eaten > self.calories_needed:
self.happiness -= 1
print("Ugh so full")
def interact_with(self, animal2):
self.happiness += self.interact_increment
print(f"Yay happy fun time with {animal2.name}")
class Cat(Animal):
"""
>>> adult = Cat("Winston", 12)
>>> adult.name
'Winston'
>>> adult.age
12
>>> adult.play_multiplier
3
>>> kitty = Cat("Kurty", 0.5)
>>> kitty.name
'Kurty'
>>> kitty.age
0.5
>>> kitty.play_multiplier
6
"""
species_name = "Domestic cat"
scientific_name = "Felis silvestris catus"
calories_needed = 200
play_multiplier = 3
def __init__(self, name, age):
super().__init__(name, age)
if self.age < 1:
self.play_multiplier = 6
class Animal:
species_name = "Animal"
scientific_name = "Animalia"
play_multiplier = 2
interact_increment = 1
def __init__(self, name, age=0):
self.name = name
self.age = age
self.calories_eaten = 0
self.happiness = 0
def play(self, num_hours):
self.happiness += (num_hours * self.play_multiplier)
print("WHEEE PLAY TIME!")
def eat(self, food):
self.calories_eaten += food.calories
print(f"Om nom nom yummy {food.name}")
if self.calories_eaten > self.calories_needed:
self.happiness -= 1
print("Ugh so full")
def interact_with(self, animal2):
self.happiness += self.interact_increment
print(f"Yay happy fun time with {animal2.name}")
class Dog(Animal):
"""
>>> spot = Dog("Spot", 5, 20)
>>> spot.name
'Spot'
>>> spot.age
5
>>> spot.weight
20
>>> spot.calories_needed
400
>>> puppy = Dog("Poppy", 1, 7)
>>> puppy.name
'Poppy'
>>> puppy.age
1
>>> puppy.weight
7
>>> puppy.calories_needed
140
"""
species_name = "Domestic dog"
scientific_name = "Canis lupus familiaris"
calories_needed = 200
def __init__(self, name, age, weight):
# Call the super class to set name and age
# Set the weight attribute
# Set calories_needed to 20x the weight
class Animal:
species_name = "Animal"
scientific_name = "Animalia"
play_multiplier = 2
interact_increment = 1
def __init__(self, name, age=0):
self.name = name
self.age = age
self.calories_eaten = 0
self.happiness = 0
def play(self, num_hours):
self.happiness += (num_hours * self.play_multiplier)
print("WHEEE PLAY TIME!")
def eat(self, food):
self.calories_eaten += food.calories
print(f"Om nom nom yummy {food.name}")
if self.calories_eaten > self.calories_needed:
self.happiness -= 1
print("Ugh so full")
def interact_with(self, animal2):
self.happiness += self.interact_increment
print(f"Yay happy fun time with {animal2.name}")
class Dog(Animal):
"""
>>> spot = Dog("Spot", 5, 20)
>>> spot.name
'Spot'
>>> spot.age
5
>>> spot.weight
20
>>> spot.calories_needed
400
>>> puppy = Dog("Poppy", 1, 7)
>>> puppy.name
'Poppy'
>>> puppy.age
1
>>> puppy.weight
7
>>> puppy.calories_needed
140
"""
species_name = "Domestic dog"
scientific_name = "Canis lupus familiaris"
calories_needed = 200
def __init__(self, name, age, weight):
super().__init__(name, age)
self.weight = weight
self.calories_needed = 20 * weight
Every Python 3 class implicitly extends the
object
class.
But we can also add in more levels ourselves.
First we define the new classes:
class Herbivore(Animal):
def eat(self, food):
if food.type == "meat":
self.happiness -= 5
else:
super().eat(food)
class Carnivore(Animal):
def eat(self, food):
if food.type == "meat":
super().eat(food)
Then we change the base classes for the subclasses:
class Rabbit(Herbivore):
class Panda(Herbivore):
class Elephant(Herbivore):
class Vulture(Carnivore):
class Lion(Carnivore):
A class may inherit from multiple base classes in Python.
First we define the new base classes:
class Predator(Animal):
def interact_with(self, other):
if other.type == "meat":
self.eat(other)
print("om nom nom, I'm a predator")
else:
super().interact_with(other)
class Prey(Animal):
type = "meat"
calories = 200
Then we inherit from them by putting both names in the parentheses:
class Rabbit(Prey, Herbivore):
class Lion(Predator, Carnivore):
Python can find the attributes in any of the base classes:
r = Rabbit("Peter", 4) # Animal __init__
r.play(1) # Animal method
r.type # Prey class variable
r.eat(Food("carrot", "veggies")) # Herbivore method
l = Lion("Scar", 12) # Animal __init__
l.eat(Food("zazu", "meat")) # Carnivore method
l.interact_with(r) # Predator method
exp0 is exp1
evaluates to True
if both exp0
and
exp1
evaluate to the same object
mufasa = Lion("Mufasa", 15)
nala = Lion("Nala", 8)
mufasa is mufasa # True
mufasa is nala # False
mufasa is not nala # True
nala is not None # True
An object can contain references to objects of other classes.
What examples of composition are in an animal conservatory?
An instance variable can refer to another instance:
class Animal:
def mate_with(self, other):
if other is not self and other.species_name == self.species_name:
self.mate = other
other.mate = self
How would we call that method?
mr_wabbit = Rabbit("Mister Wabbit", 3)
jane_doe = Rabbit("Jane Doe", 2)
mr_wabbit.mate_with(jane_doe)
An instance variable can also refer to a list of instances:
class Rabbit(Animal):
def reproduce_like_rabbits(self):
if self.mate is None:
print("oh no! better go on ZoOkCupid")
return
self.babies = []
for _ in range(0, self.num_in_litter):
self.babies.append(Rabbit("bunny", 0))
How would we call that function?
mr_wabbit = Rabbit("Mister Wabbit", 3)
jane_doe = Rabbit("Jane Doe", 2)
mr_wabbit.mate_with(jane_doe)
jane_doe.reproduce_like_rabbits()
If all instances implement a method with the same function signature, a program can rely on that method across instances of different subclasses.
def partytime(animals):
"""Assuming ANIMALS is a list of Animals, cause each
to interact with all the others exactly once."""
for i in range(len(animals)):
for j in range(i + 1, len(animals)):
animals[i].interact_with(animals[j])
How would we call that function?
elly = Elephant("Elly", 5)
pandy = Panda("PandeyBear", 4)
scar = Lion("Scar", 12)
jane_doe = Rabbit("Jane Doe", 2)
partytime([elly, pandy, scar, jane_doe])
Inheritance is best for representing "is-a" relationships
Composition is best for representing "has-a" relationships
class Parent:
def f(s):
print("Parent.f")
def g(s):
s.f()
class Child(Parent):
def f(me):
print("Child.f")
a_child = Child()
a_child.g()