{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<p style=\"text-align: right;\"> &#9989; Put your name here</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Day 18\n",
    "Pre-class Assignment: Object inheritance in Python\n",
    "\n",
    "<img src=\"https://thevpn.guru/wp-content/uploads/2016/12/Watch-PDC-World-Darts-Championship-Live-Stream-Online-e1481107242399.jpeg\" width=400px>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Goals for today's pre-class assignment\n",
    "\n",
    "</p>\n",
    "* Think about and use object inheritance in Python\n",
    "\n",
    "## Assignment instructions\n",
    "**This assignment is due by 11:59 p.m. the day before class** and should be uploaded into the appropriate \"Pre-class assignments\" submission folder in the Desire2Learn website."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "----\n",
    "# First Discussion, Inheritance\n",
    "\n",
    "Let's today talk about *inheritance*. \n",
    "\n",
    "When we make a class, we indicate the classes **parent class** in parentheses\n",
    "\n",
    "class MyClass (object):\n",
    "    pass\n",
    "\n",
    "By *parent class*, we mean that we create a *hierarchy*, an arrangement of classes in a kind of pyramid.\n",
    "\n",
    "* the top of the pyramid is the most general class. In Python, its name is `object` (note the lack of caps)\n",
    "* below the `object` class are classes that have `object` as a parent. `MyClass` is such a class\n",
    "* this hierarchy is created by the definitions of the classes\n",
    "\n",
    "class SubClass (MyClass):\n",
    "    pass\n",
    "class Sub2Class (MyClass):\n",
    "    pass\n",
    "class SubSubClass (SubClass):\n",
    "    pass\n",
    "\n",
    "<img src=\"https://i.ibb.co/Qd3KmYx/hierarchy.png\" alt=\"hierarchy\" border=\"0\">\n",
    "\n",
    "The names you can use as you move through the hierarchy can vary. Going \"up\" the hierarchy one is looking at \"parent\" or \"superclasses\", going down the hierarchy one is looking at \"child\" or \"subclasses\". \n",
    "\n",
    "The implication is that classes \"higher\" in the hierarchy are more general, and those \"lower\" are more specific, specializations if you like of the classes above. \n",
    "\n",
    "## So What? Adding to the \"find the attribute\" game\n",
    "\n",
    "The fact that you can impose an ordering on your classes is interesting, but really what is the point? We have discussed the \"find-the-attribute\" game before, we know that:\n",
    "\n",
    "* every instance knows which class it was created from. It is stored in the instances `__class__` attribute\n",
    "* to find an attribute, Python:\n",
    "    * first looks in the instance itself for the attribute\n",
    "    * if the attribute is not in the instance, Python then looks in the instance's class for the attribute\n",
    "\n",
    "To this we now add a third element:\n",
    "\n",
    "* \n",
    "    * if the attribute is not in the instance and not in the instance's class, then look to the *parent* class of the instance's class and see if the attribute is there. Continue searching classes *up the hierarchy*, as defined by the class definitions,  until you find the attribute, or upon hitting `object` Python discovers there is no such attribute and generates an error.\n",
    "\n",
    "<img src=\"https://i.ibb.co/XXT5hMg/findthemethod.png\" alt=\"findthemethod\" border=\"0\">\n",
    "\n",
    "Typically, an instance does not have a local copy of a method. The instance's class holds the methods (that's where they are defined, in the class suite), so that **all** instances of a class share the same methods. However, now it is possible to define only **some** methods in a class and allow the *find-the-method* game to discover other, already existing methods, in parent classes. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Question** What advantages would result from inheritance as described?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font size=8 color=\"#009600\">&#9998;</font> Advantages:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Unbound methods\n",
    "We have a lot of experience with calling methods via an instance. For example:\n",
    "\n",
    "class MyClass(object):\n",
    "    def __init__(self, param_str):\n",
    "        self.attribute = param_str\n",
    "    def my_method(self, new_str):\n",
    "        return self.attribute+\":\"+new_str\n",
    "my_inst = MyClass('hi there')\n",
    "print(my_inst.my_method('mom'))\n",
    "\n",
    "We know that we can create an instance and then call a method with that instance using a \"dot call\". The expression above `my_inst.my_method('mom')` demonstrates that process. When this dot-call is made, Python we bind the special variable `self` to the calling instance *automatically*, in this case to `my_inst`.\n",
    "\n",
    "However, where is the method `my_method`? As we have said, we rarely place a method in an instance. Instead we place methods in the class definition. Using the *find-the-method* game, we look first in the instance then in the instance's class, then up the parent hierarchy looking for the method. So most methods are in a class, either the instance's class or up the parent class hierarchy.\n",
    "\n",
    "## Bound vs Unbound methods\n",
    "When a method is called using the dot-call method, we say the method is **bound** to the class. For a bound method, Python automatically does the binding of the calling instance to `self`.\n",
    "\n",
    "However, there are times when **we**, the programmers need to determine what `self` is and **we** pass to the method that particular instance we want. In so doing we are making an **unbound** call. \n",
    "\n",
    "`print(MyClass.my_method(my_inst, 'mom'))`\n",
    "\n",
    "When making an unbound call, we indicate the location of the method (here, an attribute of `MyClass`) and we determine what to pass as `self`, the first argument (here `my_inst`).\n",
    "\n",
    "Why is this important?\n",
    "\n",
    "## A Class Hierarchy\n",
    "Look at the definitions below"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Pet(object):\n",
    "    species_list = []  # a class attribute!\n",
    "    def __init__(self, species=None):\n",
    "        print('Pet init')\n",
    "        self.species_str = species\n",
    "    def species(self):\n",
    "        return 'species of {:s}'.format(self.species_str)\n",
    "    def __str__(self):\n",
    "        print('Pet str')\n",
    "        return 'species of {:s}'.format(self.species_str)\n",
    "\n",
    "class Dog(Pet):\n",
    "    def __init__(self, name='', chases='Cats'):\n",
    "        print('Dog init')\n",
    "        Pet.__init__(self,'Dog')\n",
    "        self.name_str = name\n",
    "        self.chases_str = chases\n",
    "    def __str__(self):\n",
    "        print('Dog str')\n",
    "        result = Pet.__str__(self)\n",
    "        result += ', Named:{:s} who chases {:s}'.format(self.name_str, self.chases_str)\n",
    "        return result\n",
    "\n",
    "class Cat(Pet):\n",
    "    def __init__(self, name='', hates='Dogs'):\n",
    "        print('Cat init')\n",
    "        Pet.__init__(self,'Cat')\n",
    "        self.name_str = name\n",
    "        self.hates_str = hates\n",
    "    def __str__(self):\n",
    "        print('Cat str')\n",
    "        result = Pet.__str__(self)\n",
    "        result += ', Named:{:s} who hates {:s}'.format(self.name_str, self.hates_str)\n",
    "        return result"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There is something odd about lines 15 and 27 (in the `Dog` and `Cat` class respectively). In those `__init__` methods we make an unbound call to the parent class `Pet`. Why is that?\n",
    "\n",
    "If we want our subclass to have all the characteristics of the parent, then we need to make sure we *initialize* the new instance as required by the parent. To do that we need to call the parent class `__init__`. However, it must be an unbound call. Why?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Question** What if we had done the following (substituting for the `Cat __init__`)\n",
    "\n",
    "    def __init__(self, name='', hates='Dogs'):\n",
    "        self.__init__(self, 'Cat')\n",
    "        ..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font size=8 color=\"#009600\">&#9998;</font> What would happen?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Using Inheritance\n",
    "Let's see how our class hierarchy works. Note that we have put a number of `print` statements in the methods so we can follow what happens. These `print` statements are for debugging, not the actually final class (we would have to comment them out)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Dog init\n",
      "Pet init\n"
     ]
    }
   ],
   "source": [
    "my_dog = Dog('Fido')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Dog'"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_dog.species_str"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that when we made a `Dog` instance, we called the `__init__` method. Inside that method, we *explicitly* called the `Pet __init__` method so that it would be initialized as a `Pet` as well. Were there other classes above `Pet`, the process would have repeated up the hierarchy. As a result the `my_dog` instance has a species attribute created by the `Pet __init__` method"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Dog str\n",
      "Pet str\n",
      "species of Dog, Named:Fido who chases Cats\n"
     ]
    }
   ],
   "source": [
    "print(my_dog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Again, when we called the `print` function, implicitly that is a call to the `__str__` method to create a string. Thus we called the `Dog __str__` method and combined its results with the `Pet __str__` method"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'species of Dog'"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_dog.species()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There is no `species` method in `Dog`, but there is in `Pet` so we inherit the method when we make the call. In this way **all** subclasses of `Pet` have available this method and we are not required to create it in a subclass."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Do This** Add two new classes `Siamese` (subclass of `Cat`) and `Collie` (subclass of `Dog`). Put in `print` statements for debugging. Put an attribute in the instance of each that is unique to the class (hey, be creative)!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# your code here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "----\n",
    "# 2. Assignment wrap-up\n",
    "\n",
    "Please fill out the form that appears when you run the code below.  **You must completely fill this out in order to receive credit for the assignment!**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import HTML\n",
    "HTML(\n",
    "\"\"\"\n",
    "<iframe \n",
    "\tsrc=\"https://cmse.msu.edu/cmse202-pc-survey\" \n",
    "\twidth=\"800px\" \n",
    "\theight=\"600px\" \n",
    "\tframeborder=\"0\" \n",
    "\tmarginheight=\"0\" \n",
    "\tmarginwidth=\"0\">\n",
    "\tLoading...\n",
    "</iframe>\n",
    "\"\"\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---------\n",
    "### Congratulations, you're done with your pre-class assignment!\n",
    "\n",
    "Now, you just need to submit this assignment by uploading it to the course <a href=\"https://d2l.msu.edu/\">Desire2Learn</a> web page for today's submission folder (Don't forget to add your name in the first cell)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "&#169; Copyright Michigan State University Board of Trustees"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "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": 4
}
