{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h1>5. Modules and Exception Handling</h1>\n",
    "<h2>10/09/2023</h2>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h2>5.0 Last Time...</h2>\n",
    "\n",
    "<ul>\n",
    "    <li>A <b>for loop</b> lets you repeat an operation a specified  number of times.</li>\n",
    "    <li>Indentation is critical for loops in Python.</li>\n",
    "    <li>The range() function will create a list of all values up to the specified integer.</li>\n",
    "    <li>Hardcoding is the act of (often unnecessarily) restricting a piece of code so it only works under specific circumstances.</li>\n",
    "    <li>A <b>while loop</b> lets you repeat an operation until a particular condition is met.</li>\n",
    "    <li>The index in a while loop must be initialized and care should be taken to avoid infinite loops.</li>\n",
    "</ul>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h2>5.1 Modules</h2>\n",
    "\n",
    "A <b>module</b> is just a library of Python source code files that will give you access to new (frequently more specialized) functions.\n",
    "\n",
    "To import a module, the syntax is simply: <b>import \\<module name\\></b>."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-0.7568024953079283\n"
     ]
    }
   ],
   "source": [
    "# An extremely useful module in the atmospheric science world is NumPy.\n",
    "# This package lets you use a variety of mathematical (and array-based) functions and variables.\n",
    "import numpy\n",
    "\n",
    "\n",
    "# As soon as the module is imported, you have access to all its contents.\n",
    "\n",
    "# To refer to a function or variable within numpy, just follow it with a \n",
    "# period, then the function or variable in question.\n",
    "\n",
    "# As an example, numpy will let you calculate the sine of a given number.\n",
    "a = numpy.sin(4)\n",
    "print(a)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Some modules have what are called <b>submodules</b> that can be referred to in a similar way. For instance, the module NumPy has a submodule called ma, which in turn has particular functions defined within it. If you wanted to run the <b>array</b> function inside the <b>ma</b> submodule inside the <b>NumPy</b> module, it would look like <b>numpy.ma.array</b>.\n",
    "\n",
    "Modules have detailed documentation (typically easily found via Google) that will let you browse all the available functions at your disposal. This instance of Jupyter has several modules preinstalled; they may be missing if you try running them on a Python build at home. Fortunately, there are generally helpful instructions online for downloading new modules so they can be imported into your code."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-0.7568024953079282"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# If you find yourself typing the same module name again and again, \n",
    "# you can rename it to something a little quicker!\n",
    "\n",
    "import numpy as np\n",
    "\n",
    "a = np.sin(4)\n",
    "a"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As a meteorological example, let's return to our list of Celsius temperatures from yesterday. NumPy will enable us to easily calculate the maximum and minimum temperatures."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The maximum temperature is 36.5 and the minimum temperature is 13.5.\n"
     ]
    }
   ],
   "source": [
    "# First, here's our temperature list.\n",
    "\n",
    "temp = [23.5,24.5,18.0,26.8,17.7,17.0,27.1,24.6,13.5,36.5]\n",
    "\n",
    "# Let's import NumPy.\n",
    "\n",
    "import numpy as np\n",
    "\n",
    "# Now, use the max() and min() functions within the NumPy module\n",
    "# to calculate the maximum and minimum temperatures.\n",
    "\n",
    "maxT = np.max(temp)\n",
    "minT = np.min(temp)\n",
    "\n",
    "# We can even output a nice sentence summing it up!\n",
    "\n",
    "print(\"The maximum temperature is \"+str(maxT)+\" and the minimum temperature is \"+str(minT)+\".\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<b>An example!</b>\n",
    "\n",
    "In the box below, write a block of code that will apply a NumPy mathematical function of your choice to the list of temperatures <b>(temp)</b> above: https://numpy.org/doc/stable/reference/routines.math.html. Don't forget to include your module import statement!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[-0.99808203 -0.59135753 -0.75098725  0.9953511  -0.91258245 -0.96139749\n",
      "  0.92243282 -0.50789659  0.80378443 -0.93171689]\n",
      "[-0.06190529  0.80640949  0.66031671 -0.09631292  0.40889274 -0.27516334\n",
      " -0.38615761  0.86141805  0.59492066  0.36318541]\n",
      "[ 16.12272495  -0.73332164  -1.13731371 -10.33455467  -2.23183823\n",
      "   3.49391565  -2.38874697  -0.58960523   1.35107835  -2.56540287]\n"
     ]
    }
   ],
   "source": [
    "temp = [23.5,24.5,18.0,26.8,17.7,17.0,27.1,24.6,13.5,36.5]\n",
    "import numpy as np\n",
    "\n",
    "# Mathematical function #1.\n",
    "print(np.sin(temp))\n",
    "\n",
    "# Mathematical function #2.\n",
    "print(np.cos(temp))\n",
    "print(np.tan(temp))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h2>5.2 Exception Handling</h2>\n",
    "\n",
    "An \"exception\" is a very polite way of saying \"error\" in programming-speak. Often we use an if statement to determine whether something is going to cause a problem, and then use a <b>raise</b> statement to tell the code to stop and output a particular error."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "6\n",
      "12.566370614359172\n"
     ]
    }
   ],
   "source": [
    "# Let's say we have a code that will attempt to calculate the area of a circle\n",
    "# based on its radius.\n",
    "# It works well for positive values of radius, but we want to allow for the\n",
    "# possibility that someone might enter a negative radius.\n",
    "\n",
    "import numpy as np\n",
    "\n",
    "def circle_area(radius):\n",
    "    if radius < 0:\n",
    "        raise ValueError('Radius cannot be negative, you fool!')\n",
    "    area = np.pi * (radius**2)\n",
    "    return area\n",
    "\n",
    "def quadarea(base, height):\n",
    "    if base < 0:\n",
    "        raise ValueError('enter a POSITIVE number')\n",
    "    elif height < 0:\n",
    "        raise ValueError('POSITIVE integer please')\n",
    "    x = base*height\n",
    "    return x\n",
    "# Now try calling this function with a positive radius to make sure it works.\n",
    "print(quadarea(2,3))\n",
    "print(circle_area(2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "Radius cannot be negative, you fool!",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[15], line 3\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;66;03m# Now try calling this function with a negative radius.\u001b[39;00m\n\u001b[0;32m----> 3\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[43mcircle_area\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m-\u001b[39;49m\u001b[38;5;241;43m2\u001b[39;49m\u001b[43m)\u001b[49m)\n",
      "Cell \u001b[0;32mIn[14], line 10\u001b[0m, in \u001b[0;36mcircle_area\u001b[0;34m(radius)\u001b[0m\n\u001b[1;32m      8\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mcircle_area\u001b[39m(radius):\n\u001b[1;32m      9\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m radius \u001b[38;5;241m<\u001b[39m \u001b[38;5;241m0\u001b[39m:\n\u001b[0;32m---> 10\u001b[0m         \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mRadius cannot be negative, you fool!\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[1;32m     11\u001b[0m     area \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mpi \u001b[38;5;241m*\u001b[39m (radius\u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39m\u001b[38;5;241m2\u001b[39m)\n\u001b[1;32m     12\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m area\n",
      "\u001b[0;31mValueError\u001b[0m: Radius cannot be negative, you fool!"
     ]
    }
   ],
   "source": [
    "# Now try calling this function with a negative radius.\n",
    "\n",
    "print(circle_area(-2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The syntax for <b>raise</b> is the exception class (for instance, ValueError() if a value is forbidden; this is the most common type of exception you'll have, but we'll see others as we go) followed by user-defined text that will give you more information about the error.\n",
    "\n",
    "Luckily, Python also lets you build in ways to deal with common exceptions if you're pretty sure you know where the user went wrong. For example, they may have accidentally put a negative sign on the radius, but you're pretty sure they just meant the positive value. In that case, you can use a <b>try/except</b> block, which will attempt to resolve the error."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "28.274333882308138\n"
     ]
    }
   ],
   "source": [
    "# As an example, this code will call the area() function, and if it gets\n",
    "# a ValueError exception, it'll try again with the absolute value\n",
    "# of the range.\n",
    "\n",
    "radius = -3\n",
    "try:\n",
    "    a = circle_area(radius)\n",
    "except ValueError:\n",
    "    a = circle_area(abs(radius))\n",
    "    \n",
    "print(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h2>TAKE-HOME POINTS</h2>\n",
    "\n",
    "<ul>\n",
    "    <li>A <b>module</b> is a library of Python source code that gives access to new variables and functions.</li>\n",
    "    <li>Modules can be imported as whatever name you like.</li>\n",
    "    <li>A <b>raise</b> statement lets you define your own error conditions and stop code when they're met.</li>\n",
    "    <li>A <b>try/except</b> block will let you fix common issues that would otherwise result in exceptions.</li>\n",
    "</ul>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# you can import modules with any name you want"
   ]
  }
 ],
 "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
}
