{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "44ca09f7",
   "metadata": {},
   "source": [
    "## Inheritance\n",
    "\n",
    "### Motivations\n",
    "\n",
    "Let's say we're building an application that tracks students."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "feaa69a2",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class Student:\n",
    "\n",
    "    # this is a class-level variable\n",
    "    # instead of each instance having its own copy\n",
    "    # the variable is shared among all `Student`\n",
    "    next_id_counter = 1\n",
    "    \n",
    "    def __init__(self, name):\n",
    "        # assign each student a unique id\n",
    "        # note use of Student. not self.\n",
    "        self.id = Student.next_id_counter\n",
    "        Student.next_id_counter += 1\n",
    "        \n",
    "        self.name = name\n",
    "        self.year = 1\n",
    "        self.major = \"Undeclared\"\n",
    "        self.course_grades = {}\n",
    "        self.extracurriculars = []\n",
    "        \n",
    "    def add_grade(self, course_name, grade):\n",
    "        self.course_grades[course_name] = grade\n",
    "    \n",
    "    @property\n",
    "    def gpa(self):\n",
    "        grade_pts = {\"A\":4.0, \"A-\":3.7, \"B+\":3.3, \"B\":3.0, \"B-\":2.7, \"C+\":2.3, \"C\":2.0, \"C-\":1.7, \"D+\":1.3, \"D\":1.0, \"F\":0.0} \n",
    "        if len(self.course_grades) == 0:\n",
    "            return 0\n",
    "        return sum(grade_pts[g] for g in self.course_grades.values()) / len(self.course_grades)\n",
    "    \n",
    "    def __repr__(self):\n",
    "        return f\"Student(name={self.name}, id={self.id}, gpa={self.gpa})\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "c410fb1d",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "s1 = Student(\"Adam\")\n",
    "s2 = Student(\"Beth\")\n",
    "s2.add_grade(\"Programming Python\", \"A\")\n",
    "s2.add_grade(\"Discrete Math\", \"B+\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "f9deab26",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Student(name=Adam, id=8, gpa=0)\n",
      "Student(name=Beth, id=9, gpa=3.65)\n"
     ]
    }
   ],
   "source": [
    "print(s1)\n",
    "print(s2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6aa3358d",
   "metadata": {},
   "source": [
    "Perhaps we want to add `Alumni` to our application.\n",
    "\n",
    "An alum will have some things in common with students:\n",
    "\n",
    "- They still have a name.\n",
    "- We want to remember their major.\n",
    "- We'll still want to keep track of their grades/GPA.\n",
    "\n",
    "We now also:\n",
    "\n",
    "- Want to record their year of graduation.\n",
    "- No longer want to allow grades to be recorded.\n",
    "- Want to be able to calculate how long ago they graduated.\n",
    "- When displaying them, we want to display their graduation year.\n",
    "\n",
    "**How to implement?**\n",
    "\n",
    "We *could* copy `student.py` and rename to `alum.py` and rename the class as needed.\n",
    "\n",
    "**But copying & pasting is generally a bad idea!**\n",
    "\n",
    "We'd need to fix bugs & add features in both classes separately.\n",
    "\n",
    "A new feature in `Student` would need to be copied over to `Alum`, this will quickly get messy.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d0d61727",
   "metadata": {},
   "source": [
    "### Implementation\n",
    "\n",
    "Instead we will use **inheritance**, which allows us to create a new class from an existing one.  The new class inherits the attributes and methods from the parent.\n",
    "\n",
    "- **superclass**, **parent**, or **base** class: The pre-existing class.\n",
    "- **subclass**, **child**, or **derived** class: The new class that inherits the code (attributes & methods) of another class.\n",
    "\n",
    "Subclasses can extend/modify the functionality of superclasses.\n",
    "\n",
    "Syntax:\n",
    "\n",
    "```python\n",
    "class Subclass(Superclass):\n",
    "    pass\n",
    "```\n",
    "\n",
    "For example:\n",
    "\n",
    "```python\n",
    "class Alum(Student):\n",
    "    pass\n",
    "```\n",
    "\n",
    "At this point, `Alum` is a new class with the exact same implementation as `Student`.\n",
    "\n",
    "Typically we'll want to add new instance & class variables, methods, etc.\n",
    "\n",
    "Newly defined features will only apply to instances of `Alum`\n",
    "\n",
    "It is possible to override parent class behavior, or rely on parent behavior, whichever is needed."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e4d45c9f",
   "metadata": {},
   "source": [
    "### Adding & Overriding Behavior"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "47f7dfc9",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class Alum(Student):\n",
    "    def __init__(self, name, grad_year):\n",
    "        # call Student's constructor, which contains id logic\n",
    "        super().__init__(name)\n",
    "        self.graduation_year = grad_year\n",
    "        \n",
    "    # new behavior\n",
    "    def years_since_graduation(self, now):\n",
    "        return now - self.graduation_year\n",
    "    \n",
    "    # overrides parent's add_grade\n",
    "    def add_grade(self, course_name, grade):\n",
    "        raise NotImplementedError(\"cannot add grades to Alum\")\n",
    "        #print(\"Sorry, you cannot add grades to Alums\")\n",
    "        # we choose not call super().add_grade here\n",
    "    \n",
    "    # overrides parent's __repr__\n",
    "    def __repr__(self):\n",
    "        #return f\"Alum(name={self.name}, id={self.id}, gpa={self.gpa}, graduated={self.graduation_year})\"\n",
    "        string = super().__repr__()\n",
    "        string += \" is an alum\"\n",
    "        return string"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "4811b0fd",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Student(name=Charlie, id=13, gpa=0) is an alum\n",
      "6 years since graduation\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "alum1 = Alum(\"Charlie\", 2016)\n",
    "print(alum1)\n",
    "print(alum1.years_since_graduation(2022), \"years since graduation\")\n",
    "#alum1.add_grade(\"Python\", \"B\")\n",
    "alum1.gpa"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "id": "18a75666-9986-4e2b-9d55-ec0e0b9e930d",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "alum2 = Alum(\"Charlie\", 2016)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "id": "b6719a36-b827-4373-87e0-23714438b83f",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "unsupported operand type(s) for +: 'Alum' and 'Alum'",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[56], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43malum1\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[43m \u001b[49m\u001b[43malum2\u001b[49m\n",
      "\u001b[0;31mTypeError\u001b[0m: unsupported operand type(s) for +: 'Alum' and 'Alum'"
     ]
    }
   ],
   "source": [
    "alum1 + alum2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3cd672f1",
   "metadata": {},
   "source": [
    "### super()\n",
    "\n",
    "Allows direct access to parent class(es).\n",
    "\n",
    "Many different ways to be called, but for our purposes we will stick to `super().method_name()` to access parent implementation of `method_name()`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6d2c035",
   "metadata": {},
   "source": [
    "### issubclass & isinstance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "a46d5b02",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "isinstance(7, int)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "d3596a45-2c00-4541-910d-08251d3f9c5f",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# same as?\n",
    "type(7) == int"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "eaa8b6db-e7df-4137-b4c8-67dfa0be06d5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(7) == object"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "id": "9cfc8480-ecca-4f80-9dfe-70a655bc5b3c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "isinstance(7,  object)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "id": "0e50f7bc-541e-4fec-9cd6-ad708f312831",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 62,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# isinstance checks the inheritance hierarchy \n",
    "isinstance(alum2, object)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "id": "34e491d5-ac5c-4dc9-b059-491e0f0f73b0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 64,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(alum2) == Student"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "id": "cd55fd8d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 65,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "isinstance([1, 2, 3], list)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "feb9f2ac",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s1 = Student(\"Sarah\")\n",
    "isinstance(s1, Student)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "e6a2f3b1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# child classes are instances of parent types\n",
    "alum1 = Alum(\"Charlie\", 2016)\n",
    "isinstance(alum1, Student)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "8f759efb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# but not vice-versa\n",
    "isinstance(s1, Alum)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "1d52b927",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "issubclass() arg 1 must be a class",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[20], line 2\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;66;03m# takes class names\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[38;5;28;43missubclass\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43malum1\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mStudent\u001b[49m\u001b[43m)\u001b[49m\n",
      "\u001b[0;31mTypeError\u001b[0m: issubclass() arg 1 must be a class"
     ]
    }
   ],
   "source": [
    "# takes class names\n",
    "issubclass(alum1, Student)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "a71ee1cb",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "issubclass(Alum, Student)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "32e11764",
   "metadata": {},
   "source": [
    "### `object`\n",
    "\n",
    "Every object derives from a base class named `object`.\n",
    "\n",
    "```python\n",
    "class Point:\n",
    "    def __init__(self, x, y):\n",
    "        self.x = y\n",
    "\n",
    "# Same as: \n",
    "\n",
    "class Point(object):\n",
    "    def __init__(self, x, y):\n",
    "        self.x = y\n",
    "        self.y = y\n",
    "```\n",
    "\n",
    "### MRO\n",
    "\n",
    "When we call a function, Python walks up the chain of parent classes to determine the first one that has the method defined.\n",
    "\n",
    "This is called the **method resolution order**.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "id": "1d4e00ca",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on class Alum in module __main__:\n",
      "\n",
      "class Alum(Student)\n",
      " |  Alum(name, grad_year)\n",
      " |  \n",
      " |  Method resolution order:\n",
      " |      Alum\n",
      " |      Student\n",
      " |      builtins.object\n",
      " |  \n",
      " |  Methods defined here:\n",
      " |  \n",
      " |  __init__(self, name, grad_year)\n",
      " |      Initialize self.  See help(type(self)) for accurate signature.\n",
      " |  \n",
      " |  __repr__(self)\n",
      " |      Return repr(self).\n",
      " |  \n",
      " |  add_grade(self, course_name, grade)\n",
      " |      # overrides parent's add_grade\n",
      " |  \n",
      " |  years_since_graduation(self, now)\n",
      " |      # new behavior\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Readonly properties inherited from Student:\n",
      " |  \n",
      " |  gpa\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Data descriptors inherited from Student:\n",
      " |  \n",
      " |  __dict__\n",
      " |      dictionary for instance variables (if defined)\n",
      " |  \n",
      " |  __weakref__\n",
      " |      list of weak references to the object (if defined)\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Data and other attributes inherited from Student:\n",
      " |  \n",
      " |  next_id_counter = 16\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(Alum)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "id": "45f732f0-2036-416e-a44c-70b99b18a960",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(__main__.Alum, __main__.Student, object)"
      ]
     },
     "execution_count": 67,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Alum.__mro__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "592f6930-cf36-4ec1-a4f3-ea1947fa7173",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on class super in module builtins:\n",
      "\n",
      "class super(object)\n",
      " |  super() -> same as super(__class__, <first argument>)\n",
      " |  super(type) -> unbound super object\n",
      " |  super(type, obj) -> bound super object; requires isinstance(obj, type)\n",
      " |  super(type, type2) -> bound super object; requires issubclass(type2, type)\n",
      " |  Typical use to call a cooperative superclass method:\n",
      " |  class C(B):\n",
      " |      def meth(self, arg):\n",
      " |          super().meth(arg)\n",
      " |  This works for class methods too:\n",
      " |  class C(B):\n",
      " |      @classmethod\n",
      " |      def cmeth(cls, arg):\n",
      " |          super().cmeth(arg)\n",
      " |  \n",
      " |  Methods defined here:\n",
      " |  \n",
      " |  __get__(self, instance, owner=None, /)\n",
      " |      Return an attribute of instance, which is of type owner.\n",
      " |  \n",
      " |  __getattribute__(self, name, /)\n",
      " |      Return getattr(self, name).\n",
      " |  \n",
      " |  __init__(self, /, *args, **kwargs)\n",
      " |      Initialize self.  See help(type(self)) for accurate signature.\n",
      " |  \n",
      " |  __repr__(self, /)\n",
      " |      Return repr(self).\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Static methods defined here:\n",
      " |  \n",
      " |  __new__(*args, **kwargs) from builtins.type\n",
      " |      Create and return a new object.  See help(type) for accurate signature.\n",
      " |  \n",
      " |  ----------------------------------------------------------------------\n",
      " |  Data descriptors defined here:\n",
      " |  \n",
      " |  __self__\n",
      " |      the instance invoking super(); may be None\n",
      " |  \n",
      " |  __self_class__\n",
      " |      the type of the instance invoking super(); may be None\n",
      " |  \n",
      " |  __thisclass__\n",
      " |      the class invoking super()\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(super)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d2519b22",
   "metadata": {},
   "source": [
    "## Abstract Base Classes\n",
    "\n",
    "Sometimes we want to define a class that can't be instantiated directly, but is intended to be inherited from.\n",
    "\n",
    "These are known as **abstract classes**.  This helps us define an interface, which contains a collection of methods that the **concrete class** must implement.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "9f705ba6",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def print_dot_prod(v1, v2):\n",
    "    \"\"\" prints dot product between two vectors \"\"\"\n",
    "    print(v1.dot_product(v2))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec8087af",
   "metadata": {},
   "source": [
    "If we want this  method to be polymorphic for vectors of multiple dimensions, such as:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "4b20d7b3",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Vec2:\n",
    "    def __init__(self,x,y):\n",
    "        self.x = x\n",
    "        self.y = y  \n",
    "\n",
    "    def dot_product(self, other):\n",
    "        ...\n",
    "        \n",
    "class Vec3:\n",
    "    def __init__(self,x,y,z):\n",
    "        self.x = x\n",
    "        self.y = y  \n",
    "        self.z = z \n",
    "\n",
    "     def dot(self, other):\n",
    "        ..."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86d216a7",
   "metadata": {},
   "source": [
    "We can force that these types implement an interface (i.e., an abstract base class) such that we can guarantee that objects we pass to ``print_dot_prod`` will always work by forcing them to implement a ``dot_product`` method. \n",
    "\n",
    "We will define an abstract class called ``Vector`` that has only the required method: \n",
    "\n",
    "`` def dot_product(self, other) `` "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "id": "a2cddb6d",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from abc import ABC, abstractmethod\n",
    "\n",
    "class Vector(ABC):  \n",
    "    def print_x(self):\n",
    "        print(self.x)\n",
    "    \n",
    "    @abstractmethod\n",
    "    def dot_product(self, other):\n",
    "        pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "id": "a32d26a8",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "Can't instantiate abstract class Vector with abstract methods __repr__, dot_product",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[71], line 2\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;66;03m# we can't instantiate abstract classes\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m v \u001b[38;5;241m=\u001b[39m \u001b[43mVector\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n",
      "\u001b[0;31mTypeError\u001b[0m: Can't instantiate abstract class Vector with abstract methods __repr__, dot_product"
     ]
    }
   ],
   "source": [
    "# we can't instantiate abstract classes\n",
    "v = Vector()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "id": "5a9ae047",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class Vec2(Vector):\n",
    "    def __init__(self, x, y):\n",
    "        self.x = x\n",
    "        self.y = y  \n",
    "        \n",
    "    def dot_product(self, other): \n",
    "        return self.x * other.x + self.y * other.y\n",
    "        \n",
    "class Vec3(Vector):\n",
    "    def __init__(self, x, y, z):\n",
    "        self.x = x\n",
    "        self.y = y  \n",
    "        self.z = z \n",
    "        \n",
    "    def dot_product(self, other): \n",
    "        return self.x * other.x + self.y * other.y + self.z * other.z"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "id": "e25536d1",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "True\n",
      "----\n",
      "True\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "# now print_dot_prod works\n",
    "\n",
    "# Vec2 and Vec3 objects are instances of Vector since their classes \n",
    "# inherit from the Vector ABC.\n",
    "v2a = Vec2(1,2)\n",
    "v2b = Vec2(3,4)\n",
    "v3a = Vec3(6,7,3)\n",
    "v3b = Vec3(1,2,3)\n",
    "\n",
    "print(isinstance(v2a, Vec2)) \n",
    "print(isinstance(v2a, Vector)) \n",
    "print(\"----\")\n",
    "print(isinstance(v3a, Vec3)) \n",
    "print(isinstance(v3a, Vector))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "id": "69ac4241-b251-4485-9ba9-3b0348d82c34",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n"
     ]
    }
   ],
   "source": [
    "v2a.print_x()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "96d41011",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "11\n"
     ]
    }
   ],
   "source": [
    "print_dot_prod(v2a, v2b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "7cf62b14",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "29\n"
     ]
    }
   ],
   "source": [
    "print_dot_prod(v3a, v3b)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ebbd1b47-601b-4a65-b493-213ccbedf373",
   "metadata": {},
   "source": [
    "## Dataclasses\n",
    "\n",
    "Python 3.7 added `dataclasses` as a handy way to create classes that are mostly responsible for representing data. These classes often have few or no methods defined."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "id": "4f98cf62-52da-4072-bf5a-da7de3ad9a98",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from dataclasses import dataclass\n",
    "\n",
    "@dataclass\n",
    "class InventoryItem:\n",
    "    \"\"\"Class for keeping track of an item in inventory.\"\"\"\n",
    "    name: str\n",
    "    unit_price: float\n",
    "    quantity_on_hand: int = 0\n",
    "\n",
    "    def total_cost(self) -> float:\n",
    "        return self.unit_price * self.quantity_on_hand\n",
    "#InventoryItem = dataclass(InventoryItem)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af1f9445-29c7-4fd8-8648-596fa3a12006",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "8faf9c31-33e4-42f1-a8e8-8b72a7f1f96f",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "wrench = InventoryItem(\"Wrench\", 12.95, 10)\n",
    "hammer = InventoryItem(\"Hammer\", 16, 8)\n",
    "nails = InventoryItem(\"Nails\", 0.03, 1000)\n",
    "saw = InventoryItem(\"Saw\", 99)\n",
    "saw2 = InventoryItem(\"Saw\", 99)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "94f14911-335c-4097-8658-b9a30a70d53b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "saw == saw2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4a088329-2f4d-4548-a750-b0f02df5dc9b",
   "metadata": {},
   "source": [
    "Dataclasses get an automatic `__init__`, `__repr__`, `__eq__`, and several other helpful options.  (Even more is possible via the decorator: https://docs.python.org/3/library/dataclasses.html)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "id": "e3a8dd5f-55bc-49d2-af83-79c977393b0d",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "30.0"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "nails.total_cost()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d8f9a214-3277-40a9-b50c-2aabf5794079",
   "metadata": {
    "tags": []
   },
   "source": [
    "Beyond this, additional methods/staticmethods/etc. can be defined in the usual way.\n",
    "\n",
    "This syntax uses Python's type-hinting, and if you're looking to use it you'll want to get familiar with the rules\n",
    "around complex types: https://docs.python.org/3/library/typing.html\n",
    "\n",
    "**Note: This syntax has been evolving rapidly from Python 3.6->now.  This is one are where making sure you have a current (>=3.10) version of Python will matter.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "id": "3c1a8baa-3840-437d-b6ad-d880a4676423",
   "metadata": {},
   "outputs": [],
   "source": [
    "# instead of returning tuples and \n",
    "# remembering the positional order, can instead\n",
    "@dataclass\n",
    "class RetType:\n",
    "    data: list[int]\n",
    "    counter: int\n",
    "\n",
    "def fn():\n",
    "    return RetType([], counter)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7097097d-b7a6-437a-8f60-f12d38df6a1b",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "73195ac4-c0cc-4f8b-8852-2b63168e1713",
   "metadata": {},
   "outputs": [],
   "source": [
    "functools."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.10.15"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": false,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "256.933px"
   },
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
