{
 "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
}
