{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h1>13. More Adventures in OOP</h1>\n",
    "<h2>10/30/2023</h2>\n",
    "\n",
    "<h2>13.0 Last Time...</h2>\n",
    "<ul>\n",
    "    <li>A class can be created using a <b>class</b> statement followed by the name of the class.</li>\n",
    "    <li>Methods within a class definition are created using a <b>def</b> statement.</li>\n",
    "    <li><b>__init__</b> is typically the first method defined and is used to initialize the core features of the class.</li>\n",
    "</ul>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h2>13.1 OOP Example: Creating a Bibliography</h2>\n",
    "\n",
    "Let's create a new class called <b>Article</b> that's similar to our book class from last time, but stores a scientific journal article instead of a book, and writes the bibiliography entry accordingly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Start by defining a new class. (Remember, you need the 'object' argument.)\n",
    "class Article(object):\n",
    "        def __init__(self, authlast, authfirst, arttitle, journtitle, volume, pages, year):\n",
    "                self.authlast = authlast\n",
    "                self.authfirst = authfirst\n",
    "                self.arttitle = arttitle\n",
    "                self.journtitle = journtitle\n",
    "                self.volume = volume\n",
    "                self.pages = pages\n",
    "                self.year = year\n",
    "        # Let's create the make_authoryear and write_bib_entry methods from before.\n",
    "        def write_bib_entry(self):\n",
    "            \n",
    "    # We won't need any additional arguments here, since it's all handled above.\n",
    "                return self.authlast + ',' + self.authfirst + ',' + self.arttitle + ',' + self.journtitle + ',' + self.volume + ',' + self.pages + ',' + self.year\n",
    "        def authyear(self):\n",
    "                self.authyear = self.authlast + '('+self.year +')'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<__main__.Article object at 0x7f0511dc9dd0>\n",
      "brooks,harold,on the tornado thingies,journal,19,310-319,2004\n"
     ]
    }
   ],
   "source": [
    "# And a test!\n",
    "tornado = Article(\"brooks\", 'harold', \"on the tornado thingies\", \"journal\", \"19\", \"310-319\", \"2004\")\n",
    "print(tornado)\n",
    "print(tornado.write_bib_entry())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's also bring <b>Book</b> and our two instances of Book back from last lecture:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Book(object):\n",
    "    \n",
    "    def __init__(self, authlast, authfirst, \\\n",
    "                title, place, publisher, year):\n",
    "        self.authlast = authlast\n",
    "        self.authfirst = authfirst\n",
    "        self.title = title\n",
    "        self.place = place\n",
    "        self.publisher = publisher\n",
    "        self.year = year\n",
    "    \n",
    "    def write_bib_entry(self):        \n",
    "        return self.authlast \\\n",
    "            + ', ' + self.authfirst \\\n",
    "            + ', ' + self.title \\\n",
    "            + ', ' + self.place \\\n",
    "            + ': ' + self.publisher + ', '\\\n",
    "            + self.year + '.'\n",
    "    def writebibalph(self):\n",
    "        self.sortentriesalph()\n",
    "        output=''\n",
    "        for i in self.entries:\n",
    "            output = output+i.writebibalph()+\"\\n\\n\"\n",
    "        return output\n",
    "    def sortentriesalph(self):\n",
    "        self.entries = sorted(self.entries,key=op.attrgetter('authlast','authfirst'))\n",
    "beauty = Book(\"Dubay\",\"Thomas\" \\\n",
    "             , \"The Evidential Power of Beauty\" \\\n",
    "             , \"San Francisco\" \\\n",
    "             , \"Ignatius Press\", \"1999\")\n",
    "\n",
    "pynut = Book(\"Martelli\", \"Alex\" \\\n",
    "            , \"Python in a Nutshell\" \\\n",
    "            , \"Sebastopol, CA\" \\\n",
    "            , \"O'Reilly Media, Inc.\", \"2003\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's say we have a series of instances of the Book and Article classes that we want to pull together into one big bibliography. We'll create a new class called <b>Bibliography</b> for this task, and within Bibliography's definition will be two modules: one that initializes the class with everything we need, and one that sorts all entries alphabetically.\n",
    "\n",
    "To do this, we'll need some additional tools. One useful package to import here is called <b>operator</b>, which contains a useful function called <b>attrgetter</b>, which will pull a list of attributes out of an item in question. There are other ways of doing the same thing, but operator.attrgetter() will save us a lot of time! \n",
    "\n",
    "We'll also want to make use of <b>sorted()</b>, which is a function that sorts all entries (either alphabetically or numerically) according to a key we specify, which in this case will be the last name and the first name of the author (just in case we have multiple authors with the same last name)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# We'll need the operator package.\n",
    "import operator as op\n",
    "\n",
    "\n",
    "# Define our Bibliography class.\n",
    "class bib(object):\n",
    "    # Initialize the class.\n",
    "    def __init__(self,entries):\n",
    "        self.entries = entries\n",
    "        \n",
    "    # Sort the entries alphabetically.\n",
    "    def sortentriesalph(self):\n",
    "        self.entries = sorted(self.entries,key=op.attrgetter('authlast','authfirst'))\n",
    "    # Now, write a bibliography in alphabetical order.\n",
    "    def writebibalph(self):\n",
    "        self.sortentriesalph()\n",
    "        output=''\n",
    "        for i in self.entries:\n",
    "            output = output+i.writebibalph()+\"\\n\\n\"\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "ename": "AttributeError",
     "evalue": "'Book' object has no attribute 'entries'",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mAttributeError\u001b[0m                            Traceback (most recent call last)",
      "\u001b[1;32m/home/nik/data/python/notebooks/Week 06 M.ipynb Cell 9\u001b[0m line \u001b[0;36m2\n\u001b[1;32m      <a href='vscode-notebook-cell:/home/nik/data/python/notebooks/Week%2006%20M.ipynb#X11sZmlsZQ%3D%3D?line=0'>1</a>\u001b[0m a \u001b[39m=\u001b[39m bib([beauty,pynut,tornado])\n\u001b[0;32m----> <a href='vscode-notebook-cell:/home/nik/data/python/notebooks/Week%2006%20M.ipynb#X11sZmlsZQ%3D%3D?line=1'>2</a>\u001b[0m b \u001b[39m=\u001b[39m a\u001b[39m.\u001b[39;49mwritebibalph()\n\u001b[1;32m      <a href='vscode-notebook-cell:/home/nik/data/python/notebooks/Week%2006%20M.ipynb#X11sZmlsZQ%3D%3D?line=2'>3</a>\u001b[0m \u001b[39mprint\u001b[39m(b)\n",
      "\u001b[1;32m/home/nik/data/python/notebooks/Week 06 M.ipynb Cell 9\u001b[0m line \u001b[0;36m1\n\u001b[1;32m     <a href='vscode-notebook-cell:/home/nik/data/python/notebooks/Week%2006%20M.ipynb#X11sZmlsZQ%3D%3D?line=16'>17</a>\u001b[0m output\u001b[39m=\u001b[39m\u001b[39m'\u001b[39m\u001b[39m'\u001b[39m\n\u001b[1;32m     <a href='vscode-notebook-cell:/home/nik/data/python/notebooks/Week%2006%20M.ipynb#X11sZmlsZQ%3D%3D?line=17'>18</a>\u001b[0m \u001b[39mfor\u001b[39;00m i \u001b[39min\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mentries:\n\u001b[0;32m---> <a href='vscode-notebook-cell:/home/nik/data/python/notebooks/Week%2006%20M.ipynb#X11sZmlsZQ%3D%3D?line=18'>19</a>\u001b[0m     output \u001b[39m=\u001b[39m output\u001b[39m+\u001b[39mi\u001b[39m.\u001b[39;49mwritebibalph()\u001b[39m+\u001b[39m\u001b[39m\"\u001b[39m\u001b[39m\\n\u001b[39;00m\u001b[39m\\n\u001b[39;00m\u001b[39m\"\u001b[39m\n\u001b[1;32m     <a href='vscode-notebook-cell:/home/nik/data/python/notebooks/Week%2006%20M.ipynb#X11sZmlsZQ%3D%3D?line=19'>20</a>\u001b[0m \u001b[39mreturn\u001b[39;00m output\n",
      "\u001b[1;32m/home/nik/data/python/notebooks/Week 06 M.ipynb Cell 9\u001b[0m line \u001b[0;36m2\n\u001b[1;32m     <a href='vscode-notebook-cell:/home/nik/data/python/notebooks/Week%2006%20M.ipynb#X11sZmlsZQ%3D%3D?line=18'>19</a>\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mwritebibalph\u001b[39m(\u001b[39mself\u001b[39m):\n\u001b[0;32m---> <a href='vscode-notebook-cell:/home/nik/data/python/notebooks/Week%2006%20M.ipynb#X11sZmlsZQ%3D%3D?line=19'>20</a>\u001b[0m     \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49msortentriesalph()\n\u001b[1;32m     <a href='vscode-notebook-cell:/home/nik/data/python/notebooks/Week%2006%20M.ipynb#X11sZmlsZQ%3D%3D?line=20'>21</a>\u001b[0m     output\u001b[39m=\u001b[39m\u001b[39m'\u001b[39m\u001b[39m'\u001b[39m\n\u001b[1;32m     <a href='vscode-notebook-cell:/home/nik/data/python/notebooks/Week%2006%20M.ipynb#X11sZmlsZQ%3D%3D?line=21'>22</a>\u001b[0m     \u001b[39mfor\u001b[39;00m i \u001b[39min\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mentries:\n",
      "\u001b[1;32m/home/nik/data/python/notebooks/Week 06 M.ipynb Cell 9\u001b[0m line \u001b[0;36m2\n\u001b[1;32m     <a href='vscode-notebook-cell:/home/nik/data/python/notebooks/Week%2006%20M.ipynb#X11sZmlsZQ%3D%3D?line=24'>25</a>\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39msortentriesalph\u001b[39m(\u001b[39mself\u001b[39m):\n\u001b[0;32m---> <a href='vscode-notebook-cell:/home/nik/data/python/notebooks/Week%2006%20M.ipynb#X11sZmlsZQ%3D%3D?line=25'>26</a>\u001b[0m     \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mentries \u001b[39m=\u001b[39m \u001b[39msorted\u001b[39m(\u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mentries,key\u001b[39m=\u001b[39mop\u001b[39m.\u001b[39mattrgetter(\u001b[39m'\u001b[39m\u001b[39mauthlast\u001b[39m\u001b[39m'\u001b[39m,\u001b[39m'\u001b[39m\u001b[39mauthfirst\u001b[39m\u001b[39m'\u001b[39m))\n",
      "\u001b[0;31mAttributeError\u001b[0m: 'Book' object has no attribute 'entries'"
     ]
    }
   ],
   "source": [
    "a = bib([beauty,pynut,tornado])\n",
    "b = a.writebibalph()\n",
    "print(b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Why did we bother doing this? Because it really highlights the power of OOP over traditional, procedural programming. In a lot of languages, we'd have to write a function to format every source entry correctly, depending on the source type (e.g., article or book), which would result in a tree of <b>if</b> tests.\n",
    "\n",
    "Another big advantage? Adding another source type would require <b>no changes or additions to existing code</b>, just a new class definition."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h2>13.2 OOP Example: Creating a Class for Geoscience Work</h2>\n",
    "\n",
    "Let's work through another application: as an example, we'll define a class called <b>SurfaceDomain</b> that describes surface domain instances. A domain would be a land/ocean surface where the spatial extent is described by a latitude-longitude grid. We'll instantiate the class by providing a vector of longitudes and latitudes; our surface domain will be a regular grid based on those vectors. We can then assign surface parameters (e.g., elevation, temperature, roughness, etc.) as instance attributes.\n",
    "\n",
    "It may be helpful here to think of how best to represent a latitude-longitude grid in code. Let's look at the following example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let's say we have 5 longitude values and 4 latitude values.\n",
    "# We want something that's going to look like the following:\n",
    "\n",
    "# Longitude should look like this:\n",
    "\n",
    "[[0 1 2 3 4]\n",
    " [0 1 2 3 4]\n",
    " [0 1 2 3 4]\n",
    " [0 1 2 3 4]]\n",
    "\n",
    "# Latitude should look like this:\n",
    "\n",
    "[[0 0 0 0 0]\n",
    " [1 1 1 1 1]\n",
    " [2 2 2 2 2]\n",
    " [3 3 3 3 3]]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There's a function in NumPy built in for this called <b>meshgrid</b>: given a longitude array and a latitude array, it will create a nice grid combining the two.\n",
    "\n",
    "Let's start our class definition."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "class SurfaceDomain(object):\n",
    "        def __init__(self, lon, lat):\n",
    "        \n",
    "        # Let's make sure that latitude and longitude \n",
    "                self.lon = np.array(lon)\n",
    "                self.lat = np.array(lat)\n",
    "                [xall,yall] = np.meshgrid(self.lon,self.lat)\n",
    "                self.lonall = xall\n",
    "                self.latall = yall\n",
    "                del xall,yall\n",
    "        # are in array format.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can then begin to manipulate elements of this domain individually or collectively (e.g., interpolation, etc.)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1 2 3 4 5]\n",
      " [1 2 3 4 5]\n",
      " [1 2 3 4 5]\n",
      " [1 2 3 4 5]]\n",
      "[[0 0 0 0 0]\n",
      " [1 1 1 1 1]\n",
      " [2 2 2 2 2]\n",
      " [3 3 3 3 3]]\n"
     ]
    }
   ],
   "source": [
    "lon = [1,2,3,4,5]\n",
    "lat = [0,1,2,3]\n",
    "\n",
    "a = SurfaceDomain(lon,lat)\n",
    "print(a.lonall)\n",
    "print(a.latall)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h2>13.3 Take-Home Points</h2>\n",
    "<ul>\n",
    "    <li>The <b>operator</b> package enables us to use a function called <b>attrgetter()</b> to grab attribute information from various classes.</li>\n",
    "    <li>The <b>sorted()</b> function lets us sort data alphabetically or numerically as needed.</li>\n",
    "    <li>NumPy's <b>meshgrid()</b> module lets us create a grid from lat/lon vectors.</li> \n",
    "</ul>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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.11.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
