You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

660 lines
18 KiB

{
"cells": [
{
"cell_type": "markdown",
"id": "billion-medicare",
"metadata": {},
"source": [
"<!---\n",
"This lesson was developed by Steven Peters for Python five-day course.\n",
"\n",
"Questions? Comments?\n",
"1. Log an issue to this repo to alert me of a problem.\n",
"2. Suggest an edit yourself by forking this repo, making edits, and submitting a pull request with your changes back to our master branch.\n",
"3. Hit me up on Slack @steve.peters.\n",
"--->\n",
"\n",
"<h1>Python Programming: Class Inheritance</h1>\n",
"\n",
"<!--\n",
"\n",
"## Overview\n",
"This lesson assumes knowledge of classes and dives straight into the concept of inheritance. After a We Do to build a child class, there's an I Do that walks through overwriting the parent variables and methods. If there's time, assign an exercise! It's in the `xx-additional-exercises` folder (it's too long for a slide). Otherwise, assign it for homework.\n",
"\n",
"## Important Notes or Prerequisites\n",
"- Students have just learned about classes! Go slowly here. Make sure everyone's confident with classes.\n",
"\n",
"## Learning Objectives\n",
"In this lesson, students will:\n",
"\n",
"- Implement inheritance.\n",
"- Describe what has been inherited from one class to another.\n",
"- Overwrite variables and methods.\n",
"\n",
"\n",
"## Duration\n",
"20 minutes\n",
"\n",
"## Suggested Agenda\n",
"\n",
"| Time | Activity |\n",
"| --- | --- |\n",
"| 0:00 - 0:03 | Welcome |\n",
"| 0:03 - 0:10 | Creating a Child Class |\n",
"| 0:10 - 0:18 | Overwriting Variables and Methods |\n",
"| 0:18 - 0:20 | Summary |\n",
"\n",
"## Differentiation and Extensions\n",
"- In the interest of time, this does not have a You Do for overwriting attributes or methods. It has a Knowledge Check question, but feel free to add exercises!\n",
"- If students get this easily, introduce multiple inheritance — perhaps an `iPhone 8` class, inheriting from `IPhone`.\n",
"- There is, in the `xx-additional-exercises` folder in the parent folder, an inheritance challenge that you should give in class if there's time. If not, give as homework. It's quite long.\n",
"\n",
"## In Class: Materials\n",
"- Projector\n",
"- Internet connection\n",
"- Python 3\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": null,
"id": "material-playback",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "genetic-hepatitis",
"metadata": {},
"source": [
"\n",
"---\n",
"\n",
"## We Do: `IPhone` Class\n",
"\n",
"Let's create the `IPhone` class."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "sealed-aurora",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "serious-candidate",
"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": "middle-colony",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "noble-royalty",
"metadata": {},
"source": [
"\n",
"---\n",
"\n",
"## Side Discussion: Edge Cases\n",
"\n",
"Look at:\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "restricted-austin",
"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.\")"
]
},
{
"cell_type": "markdown",
"id": "communist-private",
"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": "chubby-characterization",
"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": "exempt-canal",
"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": null,
"id": "plain-muslim",
"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 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!\")"
]
},
{
"cell_type": "markdown",
"id": "bored-terminology",
"metadata": {},
"source": [
"\n",
"Try it! Then, try this. Why does it fail?\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fleet-fundamental",
"metadata": {},
"outputs": [],
"source": [
"# Let's try a Phone object on an iPhone method.\n",
"test_phone.unlock()"
]
},
{
"cell_type": "markdown",
"id": "previous-springfield",
"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": null,
"id": "advised-matter",
"metadata": {},
"outputs": [],
"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."
]
},
{
"cell_type": "markdown",
"id": "agricultural-phone",
"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": null,
"id": "exterior-heaven",
"metadata": {},
"outputs": [],
"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": "ancient-increase",
"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": "measured-popularity",
"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."
]
},
{
"cell_type": "markdown",
"id": "abroad-disney",
"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": null,
"id": "korean-pressing",
"metadata": {},
"outputs": [],
"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()"
]
},
{
"cell_type": "markdown",
"id": "complex-narrow",
"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": "encouraging-subcommittee",
"metadata": {},
"outputs": [],
"source": [
"def get_avg_price(self):\n",
" price = self.avg_sqft * 5 + self.avg_bedrooms * 15000\n",
" return price"
]
},
{
"cell_type": "markdown",
"id": "norwegian-spell",
"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": null,
"id": "reported-tragedy",
"metadata": {},
"outputs": [],
"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"
]
},
{
"cell_type": "markdown",
"id": "sacred-sweet",
"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": "mighty-stationery",
"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": "searching-database",
"metadata": {},
"source": [
"\n",
"---\n",
"\n",
"\n",
"## Knowledge Check\n",
"\n",
"Consider the following classes:\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "hundred-diameter",
"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": "dominant-venture",
"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",
"---\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
}