{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Inheritance\n",
    "\n",
    "If we're writing a new class that needs similar methods as another class, we could copy/paste those.  A better way is inheritance.  `class Child(Parent):` means that the new class named `Child` inherits methods from the previous class named `Parent`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Parent: f\n",
      "Parent: g\n",
      "Child: h\n"
     ]
    }
   ],
   "source": [
    "class Parent:\n",
    "    def f(self):\n",
    "        print(\"Parent: f\")\n",
    "\n",
    "    def g(self):\n",
    "        print(\"Parent: g\")\n",
    "\n",
    "    def h(self):\n",
    "        print(\"Parent: h\")\n",
    "\n",
    "class Child(Parent):\n",
    "    def h(self):\n",
    "        print(\"Child: h\")\n",
    "        \n",
    "youngster = Child()\n",
    "youngster.f()\n",
    "youngster.g()\n",
    "youngster.h()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In your mental model, you should imagine the `f` and `g` methods being copied to the `Child` class, like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Parent: f\n",
      "Parent: g\n",
      "Child: h\n"
     ]
    }
   ],
   "source": [
    "class Child(Parent):\n",
    "    def f(self):\n",
    "        print(\"Parent: f\")\n",
    "\n",
    "    def g(self):\n",
    "        print(\"Parent: g\")\n",
    "    \n",
    "    def h(self):\n",
    "        print(\"Child: h\")\n",
    "\n",
    "youngster = Child()\n",
    "youngster.f()\n",
    "youngster.g()\n",
    "youngster.h()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `h` method doesn't get \"copied\" from `Parent` because `Child` already has a method called that.  The technical way to describe this situation is to say that `Child` *overrides* the `h` method of the *base class* `Parent`.\n",
    "\n",
    "Every class we create has a parent.  If we don't specify, its parent is a class named `object` (yes, it is confusing that a class is named `object`, since we create objects from classes; in Python code, `object` isn't an object, it's a class).\n",
    "\n",
    "This means we always inherit some special methods, like `__str__`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'<__main__.C object at 0x7f908edd48d0>'"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "class C:\n",
    "    pass\n",
    "\n",
    "obj = C()\n",
    "\n",
    "obj.__str__() # inherited from the class named \"object\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is very convenient; otherwise, we wouldn't be able to print `obj` because doing so implicitly calls `__str__`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<__main__.C object at 0x7f908edd48d0>\n"
     ]
    }
   ],
   "source": [
    "print(obj)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Multiple Inheritance\n",
    "\n",
    "Every class we create has at least one parent (`object` at a minimum), but we're allowed to have 2, 3, or more parents.  This capability is called *multiple inheritance* -- it's a capability that many other programming languages lack."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Parent1:\n",
    "    def f(self):\n",
    "        print(\"Parent1: f\")\n",
    "        \n",
    "class Parent2:\n",
    "    def f(self):\n",
    "        print(\"Parent2: f\")\n",
    "        \n",
    "    def g(self):\n",
    "        print(\"Parent2: g\")\n",
    "        \n",
    "class Parent3:\n",
    "    def g(self):\n",
    "        print(\"Parent3: g\")\n",
    "\n",
    "class Child(Parent1, Parent2, Parent3):\n",
    "    pass\n",
    "        \n",
    "youngster = Child()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is a confusing situation!  For `f`, which method do we inherit?  Both `Parent1` and `Parent2` have an `f` method, after all.  Similarly, both `Parent2` and `Parent3` have a `g` method.\n",
    "\n",
    "Fortunately, the `Child.__mro__` tuple will tell us Python's preferences in terms of where to find methods."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(__main__.Child, __main__.Parent1, __main__.Parent2, __main__.Parent3, object)"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Child.__mro__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class '__main__.Child'>\n",
      "<class '__main__.Parent1'>\n",
      "<class '__main__.Parent2'>\n",
      "<class '__main__.Parent3'>\n",
      "<class 'object'>\n"
     ]
    }
   ],
   "source": [
    "for cls in Child.__mro__:\n",
    "    print(cls)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This means we'll use the `f` method from `Parent1` instead of the `f` method from `Parent2`, as `Parent1` is higher priority (earlier in the tuple).  Similarly, we'll use `g` from `Parent2` instead of `Parent3`.  This means we can predict what the following will print:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Parent1: f\n",
      "Parent2: g\n"
     ]
    }
   ],
   "source": [
    "youngster.f()\n",
    "youngster.g()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`__mro__` stands for \"Method Resolution Order\", and it's a *special attribute* that every class has.  Don't confuse this with the *special methods* that we've called on our objects.  Contrast `youngster.__str__()` (parentheses are necessary for method calls, special or not) with `Child.__mro__` (just an attribute, so no parentheses; also, it's a class attribute, in contrast to object attributes we more frequently encounter).\n",
    "\n",
    "Python has some rules for determining the method-resolution order:\n",
    "\n",
    "1. **prefer closer**: if both a parent class and grandparent class have the same method, take the version from the parent (which is closer to the child)\n",
    "2. **prefer left**: in `class SomeClass(Parent1, Parent2, Parent3)`, `Parent1` appears first (most to the left) in the list, so that is the highest priority; similarly, `Parent2` is higher priority than `Parent3`\n",
    "3. **prefer descendants**: if both A and B are ancestors of our class C via separate paths, and A is an ascestor of B, then B will have priority over A\n",
    "\n",
    "Situations often arise where the above rules contradict each other.  The above rules are from weakest (1) to strongest (3); this allows us to resolve contradictions.  Let's see an example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Level_1B\n"
     ]
    }
   ],
   "source": [
    "class Top:\n",
    "    def f(self):\n",
    "        print(\"Top\")\n",
    "\n",
    "class Level_1A(Top):\n",
    "    pass\n",
    "        \n",
    "class Level_1B(Top):\n",
    "    def f(self):\n",
    "        print(\"Level_1B\")\n",
    "        \n",
    "class Level_2(Level_1B):\n",
    "    pass\n",
    "\n",
    "class Level_3(Level_2):\n",
    "    pass\n",
    "\n",
    "class Bottom(Level_1A, Level_3):\n",
    "    pass\n",
    "\n",
    "b = Bottom()\n",
    "b.f()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here, `Bottom` could inherit `f` from either `Top` or `Level_1B`.  `Level_1B` is a descendent of `Top`, so the `Level_1B` version is chosen, according to rule 3.\n",
    "\n",
    "This shows the strength of rule 3.  Rule 1 would have preferred taking `f` from `Top`, as *Bottom => Level_1A => Top* is shorter than *Bottom => Level_3 => Level_2 => Level_1B => Top*.  Rule 2 would have also preferred taking `f` from `Top`, as the *Bottom => Level_1A => Top* path is follows the leftmost parents up.  Yet Rule 3 wins and `f` is taken from `Level_1B`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
