\n",
"\n",
"\n",
"\n",
"---\n",
"\n",
"## Learning Objectives\n",
"\n",
"*After this lesson, you will be able toβ¦*\n",
"\n",
"- Implement inheritance.\n",
"- Describe what has been inherited from one class to another.\n",
"- Overwrite variables and methods.\n",
"\n",
"---\n",
"\n",
"## Discussion: Similar Classes\n",
"\n",
"`Phone` is a class β there are hundreds of types of phones.\n",
"\n",
"- What attributes and functions would a `Phone` have?\n",
"\n",
"What about an `iPhone`? Or `android_phone`?\n",
"\n",
"- `iPhone` and `android_phone` would be objects of the `Phone` class.\n",
"- But, there are different types of iPhones and Android phones.\n",
"- Should `IPhone` and `AndroidPhone` be classes themselves?\n",
"\n",
"What would you do?\n",
"\n",
"---\n",
"\n",
"## Introduction: Inheritance\n",
"\n",
"`AndroidPhone` and `IPhone` are separate classes *and* in the `Phone` class.\n",
"\n",
"This is called **inheritance**: making classes that are subsets of other classes.\n",
"\n",
"`Phone` is the **parent** class. It's a regular class! All phones:\n",
"\n",
"- Have a phone number.\n",
"- Can place phone calls.\n",
"- Can send text messages.\n",
"\n",
"`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:\n",
"\n",
"- Have an `unlock` method that accepts a fingerprint.\n",
"- Have a `set_fingerprint` method that accepts a fingerprint.\n",
"\n",
"---\n",
"\n",
"## We Do: Inheritance\n",
"\n",
"All phones have a phone number, can place phone calls, and can send text messages.\n",
"\n",
"Create a new class, `Phone`, with `call` and `text` methods. Let's start and test the class:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "monetary-basket",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Calling from 5214 to 515\n",
"Sending text from 5214 to 51121\n",
"Hi!\n"
]
}
],
"source": [
"class Phone:\n",
" def __init__(self, phone_number):\n",
" self.number = phone_number\n",
"\n",
" def call(self, other_number):\n",
" print(\"Calling from\", self.number, \"to\", other_number)\n",
"\n",
" def text(self, other_number, msg):\n",
" print(\"Sending text from\", self.number, \"to\", other_number)\n",
" print(msg)\n",
"\n",
"test_phone = Phone(5214)\n",
"test_phone.call(515)\n",
"test_phone.text(51121, \"Hi!\")\n"
]
},
{
"cell_type": "markdown",
"id": "altered-tampa",
"metadata": {},
"source": [
"\n",
"---\n",
"\n",
"## We Do: `IPhone` Class\n",
"\n",
"Underneath the `Phone` class definition, let's create the `IPhone` class.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "african-respect",
"metadata": {},
"outputs": [],
"source": [
"class IPhone(Phone):\n",
" # Class definitions accept a parameter specifying what class they inherit from.\n",
" def __init__(self, phone_number):\n",
" # super()` calls the `init` defined in the parent class.\n",
" super().__init__(phone_number)\n",
" # Now self.number is set, because that's what happens in the parent Phone _init_.\n"
]
},
{
"cell_type": "markdown",
"id": "identified-fever",
"metadata": {},
"source": [
"\n",
"---\n",
"\n",
"## We Do: `IPhone` Class\n",
"\n",
"iPhones uniquely:\n",
"\n",
"- Have an `unlock` method that accepts a fingerprint.\n",
"- Have a `set_fingerprint` method that accepts a fingerprint.\n",
"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "clear-buffer",
"metadata": {},
"outputs": [],
"source": [
"class IPhone(Phone):\n",
" def __init__(self, phone_number):\n",
" super().__init__(phone_number)\n",
"\n",
" # Under the call to super, we can define unique IPhone variables.\n",
" # Regular Phone objects won't have this!\n",
" self.fingerprint = None\n",
"\n",
" # Here are methods unique to IPhone objects:\n",
" def set_fingerprint(self, fingerprint):\n",
" self.fingerprint = fingerprint\n",
"\n",
" def unlock(self, fingerprint=None):\n",
" if (fingerprint == self.fingerprint):\n",
" print(\"Phone unlocked. Fingerprint matches.\")\n",
" else:\n",
" print(\"Phone locked. Fingerprint doesn't match.\")\n"
]
},
{
"cell_type": "markdown",
"id": "personal-irrigation",
"metadata": {},
"source": [
"\n",
"---\n",
"\n",
"## Side Discussion: Edge Cases\n",
"\n",
"Look at:\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "understood-swaziland",
"metadata": {},
"outputs": [],
"source": [
"def unlock(self, fingerprint=None):\n",
" if fingerprint == self.fingerprint:\n",
" print(\"Phone unlocked. Fingerprint matches.\")\n",
" else:\n",
" print(\"Phone locked. Fingerprint doesn't match.\")\n"
]
},
{
"cell_type": "markdown",
"id": "behind-elite",
"metadata": {},
"source": [
"\n",
"What if `self.fingerprint` is currently `None`? We need to account for this!\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "circular-perth",
"metadata": {},
"outputs": [],
"source": [
"def unlock(self, fingerprint=None):\n",
" if self.fingerprint == None:\n",
" print(\"Phone unlocked. No fingerprint needed.\")\n",
" elif fingerprint == self.fingerprint:\n",
" print(\"Phone unlocked. Fingerprint matches.\")\n",
" else:\n",
" print(\"Phone locked. Fingerprint doesn't match.\")\n"
]
},
{
"cell_type": "markdown",
"id": "fantastic-champion",
"metadata": {},
"source": [
"\n",
"When programming, always watch for **edge cases**. This isn't specific to classes!\n",
"\n",
"---\n",
"\n",
"## We Do: Testing `IPhone`\n",
"\n",
"Add some test lines at the bottom:\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "permanent-peninsula",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Phone unlocked. No fingerprint needed.\n",
"Phone locked. Fingerprint doesn't match.\n",
"Phone unlocked. Fingerprint matches.\n",
"Calling from 151 to 515\n",
"Sending text from 151 to 51121\n",
"Hi!\n"
]
}
],
"source": [
"class IPhone(Phone):\n",
" def __init__(self, phone_number):\n",
" super().__init__(phone_number)\n",
"\n",
" # Under the call to super, we can define unique IPhone variables.\n",
" # Regular Phone objects won't have this!\n",
" self.fingerprint = None\n",
"\n",
" # Here are methods unique to IPhone objects:\n",
" def set_fingerprint(self, fingerprint):\n",
" self.fingerprint = fingerprint\n",
"\n",
" def unlock(self, fingerprint=None):\n",
" if self.fingerprint == None:\n",
" print(\"Phone unlocked. No fingerprint needed.\")\n",
" elif fingerprint == self.fingerprint:\n",
" print(\"Phone unlocked. Fingerprint matches.\")\n",
" else:\n",
" print(\"Phone locked. Fingerprint doesn't match.\")\n",
"\n",
"my_iphone = IPhone(151)\n",
"my_iphone.unlock()\n",
"my_iphone.set_fingerprint(\"Jory's Fingerprint\")\n",
"my_iphone.unlock()\n",
"my_iphone.unlock(\"Jory's Fingerprint\")\n",
"\n",
"# And we can call the Phone methods:\n",
"my_iphone.call(515)\n",
"my_iphone.text(51121, \"Hi!\")\n"
]
},
{
"cell_type": "markdown",
"id": "moving-wells",
"metadata": {},
"source": [
"\n",
"Try it! Then, try this. Why does it fail?\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "introductory-cleveland",
"metadata": {},
"outputs": [
{
"ename": "AttributeError",
"evalue": "'Phone' object has no attribute 'unlock'",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;31m# Let's try a Phone object on an iPhone method.\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mtest_phone\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0munlock\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[1;31mAttributeError\u001b[0m: 'Phone' object has no attribute 'unlock'"
]
}
],
"source": [
"# Let's try a Phone object on an iPhone method.\n",
"test_phone.unlock()\n"
]
},
{
"cell_type": "markdown",
"id": "professional-invitation",
"metadata": {},
"source": [
"- We can create `IPhone`s, `AndroidPhone`s, and still regular `Phone`s. `Phone` is being used as a parent class, but it's still a class!\n",
"- Inheritance is an extremely powerful feature of classes.\n",
"- It allows us to create \"generic\" parent classes, such as the `Phone()` class, and then create child classes like `IPhone()` that represent subsets of the parent class.\n",
"- Because it inherits from `Phone()`, we're still able to use the parent methods `call()` and `text()`.\n",
" - We don't need to rewrite these methods in the child class.\n",
"- Using inheritance, you can easily create hierarchies of functionality. This keeps your code clean and intuitive.\n",
"\n",
"---\n",
"\n",
"## Quick Recap: Inheritance\n",
"\n",
"- A class can inherit from another class β a parent class and a child class.\n",
"- The child class can declare its own variables and methods, but it also has access to all the parents'.\n",
"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "acting-custom",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Calling from 5214 to 515\n",
"Calling from 151 to 515\n"
]
}
],
"source": [
"## Parent class: A regular class ##\n",
"class Phone:\n",
" def __init__(self, phone_number):\n",
" self.number = phone_number\n",
"\n",
" def call(self, other_number):\n",
" print(\"Calling from\", self.number, \"to\", other_number)\n",
"\n",
"test_phone = Phone(5214) # It's a regular class!\n",
"test_phone.call(515)\n",
"\n",
"## Child class: Pass in the parent class and call super() ##\n",
"class IPhone(Phone):\n",
" def __init__(self, phone_number):\n",
" super().__init__(phone_number)\n",
"\n",
" # Under the call to super, define unique child class variables and methods.\n",
" # Parent class objects won't have this!\n",
" self.fingerprint = None\n",
"\n",
" def set_fingerprint(self, fingerprint):\n",
" self.fingerprint = fingerprint\n",
"\n",
"my_iphone = IPhone(151) # Create an object as usual.\n",
"my_iphone.set_fingerprint(\"Jory's Fingerprint\") # Call a method.\n",
"my_iphone.call(515) # Call a method from the parent class.\n"
]
},
{
"cell_type": "markdown",
"id": "gothic-overview",
"metadata": {},
"source": [
"\n",
"\n",
"---\n",
"\n",
"\n",
"## I Do: Overwriting Attributes\n",
"\n",
"**Next up: Overwriting attributes!**\n",
"\n",
"Let's switch to a new example. You don't need to follow along.\n",
"\n",
"Here's a regular `Building` class:\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "pacific-bread",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Avg. Beds: 3\n",
"Avg. Sq. Ft.: 12500\n"
]
}
],
"source": [
"class Building:\n",
" # Class variables\n",
" avg_sqft = 12500\n",
" avg_bedrooms = 3\n",
"\n",
" # No __init__ - there are no instance variables to declare!\n",
" # This is possible in any class, not just inheritance. (Building is a normal class.)\n",
"\n",
" def describe_building(self):\n",
" print('Avg. Beds:', self.avg_bedrooms)\n",
" print('Avg. Sq. Ft.:', self.avg_sqft)\n",
"\n",
" def get_avg_price(self):\n",
" price = self.avg_sqft * 5 + self.avg_bedrooms * 15000\n",
" return price\n",
"\n",
"my_building = Building()\n",
"my_building.describe_building()"
]
},
{
"cell_type": "markdown",
"id": "secondary-rebecca",
"metadata": {},
"source": [
"\n",
"---\n",
"\n",
"## I Do: Inheriting Building\n",
"\n",
"Inheriting from `Building`, we can create a `Mansion` class.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "advance-parliament",
"metadata": {},
"outputs": [],
"source": [
"# Call in the parent, Building, to the class definition.\n",
"class Mansion(Building):\n",
" # Our child class definition goes here.\n",
" # Will have the same class variables, instance variables, and methods as Mansion objects.\n"
]
},
{
"cell_type": "markdown",
"id": "smooth-craps",
"metadata": {},
"source": [
"\n",
"---\n",
"\n",
"## Overwriting Variables\n",
"\n",
"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.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "likely-profession",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Avg. Beds: 3\n",
"Avg. Sq. Ft.: 12500\n",
"Avg. Beds: 6\n",
"Avg. Sq. Ft.: 25000\n"
]
}
],
"source": [
"class Mansion(Building):\n",
" # Overwrite the class variables.\n",
" avg_sqft = 25000\n",
" avg_bedrooms = 6\n",
"\n",
" # We don't have a call to super __init__. Why?\n",
" # There's no __init__ in the parent to call!\n",
"\n",
"### Now, let's try it out. ###\n",
"# This still has the old values.\n",
"my_building = Building()\n",
"my_building.describe_building()\n",
"\n",
"# The mansion object has the new class variables!\n",
"avg_mansion = Mansion()\n",
"avg_mansion.describe_building()\n"
]
},
{
"cell_type": "markdown",
"id": "united-clinic",
"metadata": {},
"source": [
"- In this class definition, the average square feet, bedrooms, and bathrooms have been changed, but nothing else has been done.\n",
"- Because the `Mansion()` class _inherits_ from the `Building()` parent class, it has access to the class methods we defined for `Building()`.\n",
"- We don't have super because there's no `init` in the parent!\n",
"\n",
"---\n",
"\n",
"## Discussion: Child Class Methods\n",
"\n",
"In the `Building` class, we have:\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "another-intermediate",
"metadata": {},
"outputs": [],
"source": [
"def get_avg_price(self):\n",
" price = self.avg_sqft * 5 + self.avg_bedrooms * 15000\n",
" return price\n"
]
},
{
"cell_type": "markdown",
"id": "insured-integration",
"metadata": {},
"source": [
"\n",
"What if a `Mansion`'s price calculation is different? What do you think we can do?\n",
"\n",
"---\n",
"\n",
"## Overwriting Methods\n",
"\n",
"We know that we can overwrite variables. Turns out, we can also overwrite methods!\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "devoted-princess",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"107500\n"
]
},
{
"data": {
"text/plain": [
"1000000"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"class Mansion(Building):\n",
"\n",
" def get_avg_price(self):\n",
" return 1000000\n",
"\n",
"mans = Mansion()\n",
"bldg = Building()\n",
"\n",
"print(bldg.get_avg_price())\n",
"# # returns `self.avg_sqft * 5 + self.avg_bedrooms * 15000`\n",
"\n",
"mans.get_avg_price()\n",
"# Returns 1000000\n"
]
},
{
"cell_type": "markdown",
"id": "dense-japan",
"metadata": {},
"source": [
"\n",
"---\n",
"\n",
"## Quick Review\n",
"\n",
"When we make child classes, we can overwrite class variables and methods.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "underlying-collapse",
"metadata": {},
"outputs": [],
"source": [
"class Building(object):\n",
" # Class variables\n",
" avg_sqft = 12500\n",
" avg_bedrooms = 3\n",
"\n",
" def get_avg_price(self):\n",
" price = self.avg_sqft * 5 + self.avg_bedrooms * 15000\n",
" eturn price\n",
"\n",
"\n",
"class Mansion(Building):\n",
" # Overwrite the class variables.\n",
" avg_sqft = 6\n",
" avg_bedrooms = 1\n",
"\n",
" def get_avg_price(self):\n",
" return 1000000\n"
]
},
{
"cell_type": "markdown",
"id": "illegal-adelaide",
"metadata": {},
"source": [
"\n",
"---\n",
"\n",
"\n",
"## Knowledge Check\n",
"\n",
"Consider the following classes:\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "difficult-bunch",
"metadata": {},
"outputs": [],
"source": [
"class Animal(object):\n",
" def is_mammal(self):\n",
" return True\n",
" def is_alive(self):\n",
" return True\n",
"\n",
"class Grasshopper(Animal):\n",
" def is_small(self):\n",
" return True\n"
]
},
{
"cell_type": "markdown",
"id": "smart-sword",
"metadata": {},
"source": [
"\n",
"You instantiate two objects: `bug = Grasshopper()` and `cat = Animal()`. Which of the following instance methods are available for each?\n",
"\n",
"1. `is_mammal()`\n",
"2. `is_alive()`\n",
"3. `is_small()`\n",
"4. `is_animal()`\n",
"\n",
"**Answer:**\n",
"\n",
"1 and 2 for `cat`, 1-3 for `bug`.\n",
"\n",
"---\n",
"\n",
"## Summary and Q&A\n",
"\n",
"Inheritance:\n",
"\n",
"- Allows us to make classes using other classes as templates.\n",
"- Has a **parent** class (`Phone`) and a **child** class (`IPhone`).\n",
" - The parent class is still a usable class!\n",
"\n",
"Child classes:\n",
"\n",
"- `inherit` methods and properties from a parent class.\n",
"- Have access to all of the functionality of its parent.\n",
"- Can have new attributes and methods.\n",
" - They won't be available to the parent.\n",
"- Can overwrite values from the parent class."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}