---
## Learning Objectives
*After this lesson, you will be able to…*
- Implement inheritance.
- Describe what has been inherited from one class to another.
- Overwrite variables and methods.
---
## Discussion: Similar Classes
`Phone` is a class — there are hundreds of types of phones.
- What attributes and functions would a `Phone` have?
What about an `iPhone`? Or `android_phone`?
- `iPhone` and `android_phone` would be objects of the `Phone` class.
- But, there are different types of iPhones and Android phones.
- Should `IPhone` and `AndroidPhone` be classes themselves?
What would you do?
---
## Introduction: Inheritance
`AndroidPhone` and `IPhone` are separate classes *and* in the `Phone` class.
This is called **inheritance**: making classes that are subsets of other classes.
`Phone` is the **parent** class. It's a regular class! All phones:
- Have a phone number.
- Can place phone calls.
- Can send text messages.
`IPhone` is a **child** class. The child class **inherits** methods and properties from the parent class but can also define its own functionality. iPhones uniquely:
- Have an `unlock` method that accepts a fingerprint.
- Have a `set_fingerprint` method that accepts a fingerprint.
---
## We Do: Inheritance
All phones have a phone number, can place phone calls, and can send text messages.
Start a new file, `Phone.py`. In it, let's start and test the class:
```python
class Phone:
def __init__(self, phone_number):
self.number = phone_number
def call(self, other_number):
print("Calling from", self.number, "to", other_number)
def text(self, other_number, msg):
print("Sending text from", self.number, "to", other_number)
print(msg)
test_phone = Phone(5214)
test_phone.call(515)
test_phone.text(51121, "Hi!")
```
---
## We Do: `IPhone` Class
Underneath the `Phone` class definition, let's create the `IPhone` class.
```python
class IPhone(Phone):
# Class definitions accept a parameter specifying what class they inherit from.
def __init__(self, phone_number):
# super()` calls the `init` defined in the parent class.
super().__init__(phone_number)
# Now self.number is set, because that's what happens in the parent Phone _init_.
```
---
## We Do: `IPhone` Class
iPhones uniquely:
- Have an `unlock` method that accepts a fingerprint.
- Have a `set_fingerprint` method that accepts a fingerprint.
```python
class IPhone(Phone):
def __init__(self, phone_number):
super().__init__(phone_number)
# Under the call to super, we can define unique IPhone variables.
# Regular Phone objects won't have this!
self.fingerprint = None
# Here are methods unique to IPhone objects:
def set_fingerprint(self, fingerprint):
self.fingerprint = fingerprint
def unlock(self, fingerprint=None):
if (fingerprint == self.fingerprint):
print("Phone unlocked. Fingerprint matches.")
else:
print("Phone locked. Fingerprint doesn't match.")
```
---
## Side Discussion: Edge Cases
Look at:
```python
def unlock(self, fingerprint=None):
if (fingerprint == self.fingerprint):
print("Phone unlocked. Fingerprint matches.")
else:
print("Phone locked. Fingerprint doesn't match.")
```
What if `self.fingerprint` is currently `None`? We need to account for this!
```python
def unlock(self, fingerprint=None):
if (self.fingerprint == None):
print("Phone unlocked. No fingerprint needed.")
elif (fingerprint == self.fingerprint):
print("Phone unlocked. Fingerprint matches.")
else:
print("Phone locked. Fingerprint doesn't match.")
```
When programming, always watch for **edge cases**. This isn't specific to classes!
---
## We Do: Testing `IPhone`
Add some test lines at the bottom:
```python
my_iphone = IPhone(151)
my_iphone.unlock()
my_iphone.set_fingerprint("Jory's Fingerprint")
my_iphone.unlock()
my_iphone.unlock("Jory's Fingerprint")
# And we can call the Phone methods:
my_iphone.call(515)
my_iphone.text(51121, "Hi!")
```
Try it! Then, try this. Why does it fail?
```python
# Let's try a Phone object on an iPhone method.
test_phone.unlock()
```
---
## Quick Recap: Inheritance
- A class can inherit from another class — a parent class and a child class.
- The child class can declare its own variables and methods, but it also has access to all the parents'.
```python
## Parent class: A regular class ##
class Phone:
def __init__(self, phone_number):
self.number = phone_number
def call(self, other_number):
print("Calling from", self.number, "to", other_number)
test_phone = Phone(5214) # It's a regular class!
test_phone.call(515)
## Child class: Pass in the parent class and call super() ##
class IPhone(Phone):
def __init__(self, phone_number):
super().__init__(phone_number)
# Under the call to super, define unique child class variables and methods.
# Parent class objects won't have this!
self.fingerprint = None
def set_fingerprint(self, fingerprint):
self.fingerprint = fingerprint
my_iphone = IPhone(151) # Create an object as usual.
my_iphone.set_fingerprint("Jory's Fingerprint") # Call a method.
my_iphone.call(515) # Call a method from the parent class.
```
---
## I Do: Overwriting Attributes
**Next up: Overwriting attributes!**
Let's switch to a new example. You don't need to follow along.
Here's a regular `Building` class:
```python
class Building(object):
# Class variables
avg_sqft = 12500
avg_bedrooms = 3
# No __init__ - there are no instance variables to declare!
# This is possible in any class, not just inheritance. (Building is a normal class.)
def describe_building(self):
print('Avg. Beds:', self.avg_bedrooms)
print('Avg. Sq. Ft.:', self.avg_sqft)
def get_avg_price(self):
price = self.avg_sqft * 5 + self.avg_bedrooms * 15000
return price
my_building = Building()
my_building.describe_building()
```
---
## I Do: Inheriting Building
Inheriting from `Building`, we can create a `Mansion` class.
```python
# Call in the parent, Building, to the class definition.
class Mansion(Building):
# Our child class definition goes here.
# Will have the same class variables, instance variables, and methods as Mansion objects.
```
---
## Overwriting Variables
What if we want the class variables to have different values? We can set new ones. Remember, child classes do not affect the parent class.
```python
class Mansion(Building):
# Overwrite the class variables.
avg_sqft = 6
avg_bedrooms = 1
# We don't have a call to super __init__. Why?
# There's no __init__ in the parent to call!
### Now, let's try it out. ###
# This still has the old values.
my_building = Building()
my_building.describe_building()
# The mansion object has the new class variables!
avg_mansion = Mansion()
avg_mansion.describe_building()
```
---
## Discussion: Child Class Methods
In the `Building` class, we have:
```python
def get_avg_price(self):
price = self.avg_sqft * 5 + self.avg_bedrooms * 15000
return price
```
What if a `Mansion`'s price calculation is different? What do you think we can do?
---
## Overwriting Methods
We know that we can overwrite variables. Turns out, we can also overwrite methods!
```python
class Mansion(Building):
def get_avg_price(self):
return 1000000
mans = Mansion()
bldg = Building()
bldg.get_avg_price()
# # returns `self.avg_sqft * 5 + self.avg_bedrooms * 15000`
mans.get_avg_price()
# Returns 1000000
```
---
## Quick Review
When we make child classes, we can overwrite class variables and methods.
```python
class Building(object):
# Class variables
avg_sqft = 12500
avg_bedrooms = 3
def get_avg_price(self):
price = self.avg_sqft * 5 + self.avg_bedrooms * 15000
return price
class Mansion(Building):
# Overwrite the class variables.
avg_sqft = 6
avg_bedrooms = 1
def get_avg_price(self):
return 1000000
```
---
## Knowledge Check
Consider the following classes:
```python
class Animal(object):
def is_mammal(self):
return True
def is_alive(self):
return True
class Grasshopper(Animal):
def is_small(self):
return True
```
You instantiate two objects: `bug = Grasshopper()` and `cat = Animal()`. Which of the following instance methods are available for each?
1. `bug.is_mammal()`
2. `bug.is_alive()`
3. `bug.is_small()`
4. `bug.is_animal()`
---
## Summary and Q&A
Inheritance:
- Allows us to make classes using other classes as templates.
- Has a **parent** class (`Phone`) and a **child** class (`IPhone`).
- The parent class is still a usable class!
Child classes:
- `inherit` methods and properties from a parent class.
- Have access to all of the functionality of its parent.
- Can have new attributes and methods.
- They won't be available to the parent.
- Can overwrite values from the parent class.