{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "e4402c74",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "# Implementing Operators in your Classes and Hiding Attributes\n",
    "#### CS 66: Introduction to Computer Science II"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aab487a8",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## References for this lecture\n",
    "\n",
    "Problem Solving with Algorithms and Data Structures using Python\n",
    "\n",
    "Section 1.13: [https://runestone.academy/ns/books/published/pythonds/Introduction/ObjectOrientedProgramminginPythonDefiningClasses.html](https://runestone.academy/ns/books/published/pythonds/Introduction/ObjectOrientedProgramminginPythonDefiningClasses.html)\n",
    "\n",
    "Section 2.1: [https://runestone.academy/ns/books/published/pythonds/ProperClasses/a_proper_python_class.html](https://runestone.academy/ns/books/published/pythonds/ProperClasses/a_proper_python_class.html)\n",
    "\n",
    "Section 2.2 appears to be missing from the book, but we'll cover that too"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ce033d7e",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Where we left off\n",
    "\n",
    "Last time, we made a class for representing measurements in feet-inches. \n",
    "\n",
    "We just implemented the special `__add__()` method, which will make it work with the `+` operator."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "58ed1f94",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "6ft. 0in.\n"
     ]
    }
   ],
   "source": [
    "class FeetInches:\n",
    "    \n",
    "    def __init__(self,f,i):\n",
    "        self.feet = f\n",
    "        self.inches = i\n",
    "        \n",
    "    def simplify(self):\n",
    "        \"\"\"\n",
    "        if the number of inches is > 12, \n",
    "        this regroups the excess into feet\n",
    "        \"\"\"\n",
    "        self.feet += self.inches // 12\n",
    "        self.inches = self.inches % 12\n",
    "    \n",
    "    def __repr__(self):\n",
    "        return str(self.feet)+\"ft. \"+str(self.inches)+\"in.\"\n",
    "    \n",
    "    def __add__(self,other_measurement):\n",
    "        total_feet = self.feet + other_measurement.feet\n",
    "        total_inches = self.inches + other_measurement.inches\n",
    "        \n",
    "        #create an new FeetInches object with the new measurements\n",
    "        total_FI = FeetInches(total_feet,total_inches)\n",
    "        total_FI.simplify()\n",
    "        return total_FI\n",
    "    \n",
    "\n",
    "measurement1 = FeetInches(3,6)\n",
    "measurement2 = FeetInches(2,6)\n",
    "total = measurement1 + measurement2\n",
    "print(total)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0b740e1e",
   "metadata": {},
   "source": [
    "Similarly, you can support any of the following operators (plus more that I haven't listed):\n",
    "\n",
    "* `+`: `object.__add__(self, other)`   \n",
    "* `-`: `object.__sub__(self, other)`   \n",
    "* `*`: `object.__mul__(self, other)`   \n",
    "* `/`: `object.__truediv__(self, other)`   \n",
    "* `//`: `object.__floordiv__(self, other)`\n",
    "* `%`: `object.__mod__(self, other)`\n",
    "* `**`: `object.__pow__(self, other)`\n",
    "* `<<`: `object.__lshift__(self, other)`\n",
    "* `>>`: `object.__rshift__(self, other)`\n",
    "* `&`: `object.__and__(self, other)` (this is not the logical `and` operator)\n",
    "* `^`: `object.__xor__(self, other)`\n",
    "* `|`: `object.__or__(self, other)` (this is not the logical `or` operator)\n",
    "* `<`: `object.__lt__(self, other)`\n",
    "* `<=`: `object.__le__(self, other)`\n",
    "* `==`: `object.__eq__(self, other)`\n",
    "* `!=`: `object.__ne__(self, other)`\n",
    "* `>`: `object.__gt__(self, other)`\n",
    "* `>=`: `object.__ge__(self, other)`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1b141f7b",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Group Activity Problem 1\n",
    "\n",
    "Add support for the subtraction operator to the `FeetInches` class."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cb4aa9d9",
   "metadata": {},
   "source": [
    "## Group Activity Problem 2\n",
    "\n",
    "Discussion the following questions.\n",
    "* What is the name of the function would I have to define (i.e., like `__add__` is for `+`) to be able to compare two `FeetInches` objects like in the code below?\n",
    "* Note that `__add__` and `__sub__` both return a new object of type `FeetInches`. What type of value should the function for `<` return?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6336b026",
   "metadata": {},
   "outputs": [],
   "source": [
    "measurement1 = FeetInches(3,6)\n",
    "measurement2 = FeetInches(2,6)\n",
    "print(measurement1 < measurement2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc6781c8",
   "metadata": {},
   "source": [
    "## Group Activity Problem 3\n",
    "\n",
    "Add support for the `<` (less than) operator to the `FeetInches` class."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a4cad623",
   "metadata": {},
   "source": [
    "## Group Activity Problem 4\n",
    "\n",
    "After you implement `<`, you might get some other operators like `>` for free. Try the code below and see if it works with your definition. Do `<=`, `==`, `!=`, `>=` work? Why or why not?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "933451b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "measurement1 = FeetInches(3,6)\n",
    "measurement2 = FeetInches(2,6)\n",
    "print(measurement1 > measurement2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "98254720",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "We'll use this space to go over the solution to the group activities above."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4d219a36",
   "metadata": {},
   "outputs": [],
   "source": [
    "class FeetInches:\n",
    "    \n",
    "    def __init__(self,f,i):\n",
    "        self.feet = f\n",
    "        self.inches = i\n",
    "        \n",
    "    def simplify(self):\n",
    "        \"\"\"\n",
    "        if the number of inches is > 12, \n",
    "        this regroups the excess into feet\n",
    "        \"\"\"\n",
    "        self.feet += self.inches // 12\n",
    "        self.inches = self.inches % 12\n",
    "    \n",
    "    def __repr__(self):\n",
    "        return str(self.feet)+\"ft. \"+str(self.inches)+\"in.\"\n",
    "    \n",
    "    def __add__(self,other_measurement):\n",
    "        total_feet = self.feet + other_measurement.feet\n",
    "        total_inches = self.inches + other_measurement.inches\n",
    "        \n",
    "        #create an new FeetInches object with the new measurements\n",
    "        total_FI = FeetInches(total_feet,total_inches)\n",
    "        total_FI.simplify()\n",
    "        return total_FI\n",
    "    \n",
    "\n",
    "measurement1 = FeetInches(3,6)\n",
    "measurement2 = FeetInches(2,6)\n",
    "total = measurement1 + measurement2\n",
    "print(total)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9bd43682",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## A Playing Card Class\n",
    "\n",
    "Let's say we want to create a class for representing playing cards (so we can implement games like War, Bridge, Black Jack, etc.).\n",
    "\n",
    "Here is a bare-bones implementation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "39a669e5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Here's what the card looks like: <__main__.PlayingCard object at 0x107713370>\n"
     ]
    }
   ],
   "source": [
    "class PlayingCard:\n",
    "    \n",
    "    def __init__(self,v,s):\n",
    "        self.value = v\n",
    "        self.suit = s\n",
    "        \n",
    "two_of_clubs = PlayingCard(2,\"♣\")\n",
    "two_of_hearts = PlayingCard(2,\"♡\")\n",
    "ten_of_hearts = PlayingCard(10,\"♡\")\n",
    "seven_of_spades = PlayingCard(7,\"♠\")\n",
    "four_of_diamonds = PlayingCard(4,\"♢\")\n",
    "\n",
    "print(\"Here's what the card looks like:\",ten_of_hearts)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c7d2d872",
   "metadata": {},
   "source": [
    "Note that ♣, ♡, ♠, and ♢ are all just text characters (see [https://en.wikipedia.org/wiki/Playing_cards_in_Unicode](https://en.wikipedia.org/wiki/Playing_cards_in_Unicode) ). You can use C, H, S, and D if you prefer."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f362bb78",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "## Group Activity Problem 5\n",
    "\n",
    "How should we handle cards without a numic value like Queens or Aces?\n",
    "\n",
    "What do we have to do to get the cards to print nicely?\n",
    "\n",
    "What do we have to do to allow us to write code like this?\n",
    "\n",
    "Copy the code above and begin adding support for these."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5dfad16f",
   "metadata": {},
   "outputs": [],
   "source": [
    "if two_of_clubs < ten_of_hearts:\n",
    "    print(\"Player 2 wins the hand\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1ec04e3d",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "515d1471",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Controlling Access\n",
    "\n",
    "When designing a class, you should plan for how to protect against accidental misuse. \n",
    "\n",
    "For example, what happens if someone tries to do this?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ad719035",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "outputs": [],
   "source": [
    "pikachu = PlayingCard(\"Pikachu\",40)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f2471b63",
   "metadata": {},
   "source": [
    "This particular class is only meant to represent standard French playing cards, so this should cause some kind of error.\n",
    "\n",
    "One way to handle it might be like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "1f70f35a",
   "metadata": {},
   "outputs": [
    {
     "ename": "Exception",
     "evalue": "A PlayingCard's value must be an integer in the range 2-14.",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mException\u001b[0m                                 Traceback (most recent call last)",
      "Input \u001b[0;32mIn [2]\u001b[0m, in \u001b[0;36m<cell line: 10>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      7\u001b[0m         \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mvalue \u001b[38;5;241m=\u001b[39m v\n\u001b[1;32m      8\u001b[0m         \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msuit \u001b[38;5;241m=\u001b[39m s\n\u001b[0;32m---> 10\u001b[0m twentyseven_of_clubs \u001b[38;5;241m=\u001b[39m \u001b[43mPlayingCard\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m27\u001b[39;49m\u001b[43m,\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m♣\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m     11\u001b[0m pikachu \u001b[38;5;241m=\u001b[39m PlayingCard(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mPikachu\u001b[39m\u001b[38;5;124m\"\u001b[39m,\u001b[38;5;241m40\u001b[39m)\n",
      "Input \u001b[0;32mIn [2]\u001b[0m, in \u001b[0;36mPlayingCard.__init__\u001b[0;34m(self, v, s)\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__init__\u001b[39m(\u001b[38;5;28mself\u001b[39m,v,s):\n\u001b[1;32m      5\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mtype\u001b[39m(v) \u001b[38;5;241m!=\u001b[39m \u001b[38;5;28mtype\u001b[39m(\u001b[38;5;241m1\u001b[39m) \u001b[38;5;129;01mor\u001b[39;00m v \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m14\u001b[39m \u001b[38;5;129;01mor\u001b[39;00m v \u001b[38;5;241m<\u001b[39m \u001b[38;5;241m2\u001b[39m:\n\u001b[0;32m----> 6\u001b[0m         \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mA PlayingCard\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124ms value must be an integer in the range 2-14.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m      7\u001b[0m     \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mvalue \u001b[38;5;241m=\u001b[39m v\n\u001b[1;32m      8\u001b[0m     \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msuit \u001b[38;5;241m=\u001b[39m s\n",
      "\u001b[0;31mException\u001b[0m: A PlayingCard's value must be an integer in the range 2-14."
     ]
    }
   ],
   "source": [
    "class PlayingCard:\n",
    "    \n",
    "    def __init__(self,v,s):\n",
    "        \n",
    "        if type(v) != type(1) or v > 14 or v < 2:\n",
    "            raise Exception(\"A PlayingCard's value must be an integer in the range 2-14.\")\n",
    "        self.value = v\n",
    "        self.suit = s\n",
    "        \n",
    "twentyseven_of_clubs = PlayingCard(27,\"♣\")\n",
    "pikachu = PlayingCard(\"Pikachu\",40)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6d582468",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Hiding Attributes\n",
    "\n",
    "By convention, any attribute or method whose name starts with an underscore should be treated as __private__, meaning you shouldn't change the variable outside the class.\n",
    "\n",
    "`self._value`\n",
    "\n",
    "\n",
    "If it doesn't start with an underscore, it is __public__ and changing it outside the class is fair game.\n",
    "\n",
    "If you start it with _two_ underscores, then Python performs name mangling, which doesn't let you change the name outside the class (unless you do something extra to get around the mangling).\n",
    "\n",
    "`self.__value`\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "02f5c4ce",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2♣\n"
     ]
    }
   ],
   "source": [
    "class PlayingCard:\n",
    "    \n",
    "    def __init__(self,v,s):\n",
    "        \n",
    "        if type(v) != type(1) or v > 14 or v < 2:\n",
    "            raise Exception(\"A PlayingCard's value must be an integer in the range 2-14.\")\n",
    "        self.__value = v\n",
    "        self.__suit = s\n",
    "        \n",
    "    def __repr__(self):\n",
    "        return str(self.__value)+str(self.__suit)\n",
    "        \n",
    "pikachu = PlayingCard(2,\"♣\")\n",
    "pikachu.__value = \"Pikachu\"  #this doesn't actually change self.__value\n",
    "pikachu.__suit = 40\n",
    "print(pikachu)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0e8b966",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "## Group Activity Problem 6\n",
    "\n",
    "Update your class so that `self.value` and `self.suit` are hidden (private/mangled)."
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Slideshow",
  "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.6"
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
