{ "cells": [ { "cell_type": "markdown", "id": "billion-medicare", "metadata": {}, "source": [ "\n", "\n", "

Python Programming: Class Inheritance

\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": 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 }