{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<small><small><i>\n",
    "All the IPython Notebooks in **Python Files** lecture series by **[Dr. Milaan Parmar](https://www.linkedin.com/in/milaanparmar/)** are available @ **[GitHub](https://github.com/milaan9/05_Python_Files)**\n",
    "</i></small></small>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Python Exceptions Handling Using `try`, `except` and `finally` statement\n",
    "\n",
    "In this class, you'll learn how to handle exceptions in your Python program using try, except and finally statements with the help of examples."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exceptions in Python\n",
    "\n",
    "An **exception** is an event, which occurs during the execution of a program that disrupts the normal flow of the program's instructions. In general, when a Python script encounters a situation that it cannot cope with, it raises an exception. An exception is a Python object that **represents an error**.\n",
    "\n",
    "Python has many **[built-in exceptions](https://github.com/milaan9/05_Python_Files/blob/main/003_Python_File_Exception.ipynb)** that are raised when your program encounters an error (something in the program goes wrong).\n",
    "\n",
    "When a Python script raises an exception, it must either handle the exception immediately otherwise it **terminates** and **quits**.\n",
    "\n",
    "When these exceptions occur, the Python interpreter stops the current process and passes it to the calling process until it is handled. If not handled, the program will crash.\n",
    "\n",
    "For example, let us consider a program where we have a function **`A`** that calls **[function](https://github.com/milaan9/04_Python_Functions/blob/main/001_Python_Functions.ipynb)** **`B`**, which in turn calls function **`C`**. If an exception occurs in function **`C`** but is not handled in **`C`**, the exception passes to **`B`** and then to **`A`**.\n",
    "\n",
    "If never handled, an error message is displayed and our program comes to a sudden unexpected halt."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Catching Exceptions in Python\n",
    "\n",
    "In Python, exceptions can be handled using a **`try`** statement.\n",
    "\n",
    "The critical operation which can raise an exception is placed inside the **`try`** clause. The code that handles the exceptions is written in the **`except`** clause.\n",
    "\n",
    "We can thus choose what operations to perform once we have caught the exception. Here is a simple example."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:22:14.133537Z",
     "start_time": "2021-10-18T18:22:14.121821Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Something went wrong\n"
     ]
    }
   ],
   "source": [
    "# Example 1:\n",
    "\n",
    "try:\n",
    "    print(10 + '5')\n",
    "except:\n",
    "    print('Something went wrong')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Explanation:**\n",
    "\n",
    "In the example above the second operand is a string. We could change it to float or int to add it with the number to make it work. But without any changes, the second block, **`except`**, will be executed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:22:14.164787Z",
     "start_time": "2021-10-18T18:22:14.138420Z"
    },
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The entry is a\n",
      "Oops! <class 'ValueError'> occurred.\n",
      "Next entry.\n",
      "\n",
      "The entry is 0\n",
      "Oops! <class 'ZeroDivisionError'> occurred.\n",
      "Next entry.\n",
      "\n",
      "The entry is 2\n",
      "The reciprocal of 2 is 0.5\n"
     ]
    }
   ],
   "source": [
    "# Example 2:\n",
    "\n",
    "# import module sys to get the type of exception\n",
    "import sys\n",
    "\n",
    "randomList = ['a', 0, 2]\n",
    "\n",
    "for entry in randomList:\n",
    "    try:\n",
    "        print(\"The entry is\", entry)\n",
    "        r = 1/int(entry)\n",
    "        break\n",
    "    except:\n",
    "        print(\"Oops!\", sys.exc_info()[0], \"occurred.\")\n",
    "        print(\"Next entry.\")\n",
    "        print()\n",
    "print(\"The reciprocal of\", entry, \"is\", r)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Explanation:**\n",
    "\n",
    "In this program, we loop through the values of the **`randomList`** list. As previously mentioned, the portion that can cause an exception is placed inside the **`try`** block.\n",
    "\n",
    "If no exception occurs, the **`except`** block is skipped and normal flow continues(for last value). But if any exception occurs, it is caught by the **`except`** block (first and second values).\n",
    "\n",
    "Here, we print the name of the exception using the **`exc_info()`** function inside sys module. We can see that a causes **`ValueError`** and **`0`** causes **`ZeroDivisionError`**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since every exception in Python inherits from the base **`Exception`** class, we can also perform the above task in the following way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:22:14.257073Z",
     "start_time": "2021-10-18T18:22:14.167716Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The entry is a\n",
      "Oops! <class 'ValueError'> occurred.\n",
      "Next entry.\n",
      "\n",
      "The entry is 0\n",
      "Oops! <class 'ZeroDivisionError'> occurred.\n",
      "Next entry.\n",
      "\n",
      "The entry is 2\n",
      "The reciprocal of 2 is 0.5\n"
     ]
    }
   ],
   "source": [
    "# Example 3:\n",
    "\n",
    "# This program has the same output as the above program.\n",
    "\n",
    "# import module sys to get the type of exception\n",
    "import sys\n",
    "\n",
    "randomList = ['a', 0, 2]\n",
    "\n",
    "for entry in randomList:\n",
    "    try:\n",
    "        print(\"The entry is\", entry)\n",
    "        r = 1/int(entry)\n",
    "        break\n",
    "    except Exception as e:\n",
    "        print(\"Oops!\", e.__class__, \"occurred.\")\n",
    "        print(\"Next entry.\")\n",
    "        print()\n",
    "print(\"The reciprocal of\", entry, \"is\", r)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:22:21.569073Z",
     "start_time": "2021-10-18T18:22:14.260978Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Enter your name: Milaan\n",
      "Year you were born: 1926\n",
      "Something went wrong\n"
     ]
    }
   ],
   "source": [
    "# Example 4:\n",
    "\n",
    "try:\n",
    "    name = input('Enter your name: ')\n",
    "    year_born = input('Year you were born: ')\n",
    "    age = 2022 - year_born\n",
    "    print(f'You are {name}. And your age is {age}.')\n",
    "except:\n",
    "    print('Something went wrong')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Explanation:**\n",
    "\n",
    "In the above example, the exception block will run and we do not know exactly the problem. To analyze the problem, we can use the different error types with except."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the following example, it will handle the error and will also tell us the kind of error raised."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:22:33.280977Z",
     "start_time": "2021-10-18T18:22:21.572006Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Enter your name: Milaan\n",
      "Year you were born: 1926\n",
      "Type error occured\n"
     ]
    }
   ],
   "source": [
    "# Example 5:\n",
    "\n",
    "try:\n",
    "    name = input('Enter your name: ')\n",
    "    year_born = input('Year you were born: ')\n",
    "    age = 2022 - year_born\n",
    "    print(f'You are {name}. And your age is {age}.')\n",
    "except TypeError:\n",
    "    print('Type error occured')\n",
    "except ValueError:\n",
    "    print('Value error occured')\n",
    "except ZeroDivisionError:\n",
    "    print('zero division error occured')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Catching Specific Exceptions in Python\n",
    "\n",
    "In the above example, we did not mention any specific exception in the **`except`** clause.\n",
    "\n",
    "This is not a good programming practice as it will catch all exceptions and handle every case in the same way. We can specify which exceptions an **`except`** clause should catch.\n",
    "\n",
    "A **`try`** clause can have any number of **`except`** clauses to handle different exceptions, however, only one will be executed in case an exception occurs.\n",
    "\n",
    "We can use a tuple of values to specify multiple exceptions in an **`except`** clause. Here is an example pseudo code.\n",
    "\n",
    "```python\n",
    ">>> try:\n",
    ">>>    # do something\n",
    ">>>    pass\n",
    "\n",
    ">>> except ValueError:\n",
    ">>>    # handle ValueError exception\n",
    ">>>    pass\n",
    "\n",
    ">>> except (TypeError, ZeroDivisionError):\n",
    ">>>    # handle multiple exceptions\n",
    ">>>    # TypeError and ZeroDivisionError\n",
    ">>>    pass\n",
    "\n",
    ">>> except:\n",
    ">>>    # handle all other exceptions\n",
    ">>>    pass\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Raising Exceptions in Python\n",
    "\n",
    "In Python programming, exceptions are raised when errors occur at runtime. We can also manually **`raise`** exceptions using the raise keyword.\n",
    "\n",
    "We can optionally pass values to the exception to clarify why that exception was raised."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:22:33.727265Z",
     "start_time": "2021-10-18T18:22:33.282934Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "ename": "KeyboardInterrupt",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-6-c761920b81b0>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mKeyboardInterrupt\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m: "
     ]
    }
   ],
   "source": [
    "raise KeyboardInterrupt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:22:39.742401Z",
     "start_time": "2021-10-18T18:22:39.721896Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "ename": "MemoryError",
     "evalue": "This is an argument",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mMemoryError\u001b[0m                               Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-7-51782e52f201>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mMemoryError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"This is an argument\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mMemoryError\u001b[0m: This is an argument"
     ]
    }
   ],
   "source": [
    "raise MemoryError(\"This is an argument\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:22:50.194545Z",
     "start_time": "2021-10-18T18:22:40.446502Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Enter a positive integer: 9\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    a = int(input(\"Enter a positive integer: \"))\n",
    "    if a <= 0:\n",
    "        raise ValueError(\"That is not a positive number!\")\n",
    "except ValueError as ve:\n",
    "    print(ve)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Python `try` with `else` clause\n",
    "\n",
    "In some situations, you might want to run a certain block of code if the code block inside `try` ran without any errors. For these cases, you can use the optional `else` keyword with the `try` statement.\n",
    "\n",
    ">**Note**: Exceptions in the else clause are not handled by the preceding except clauses.\n",
    "\n",
    "```python\n",
    ">>> try:\n",
    ">>>    You do your operations here\n",
    ">>>    ......................\n",
    ">>> except ExceptionI:\n",
    ">>>    If there is ExceptionI, then execute this block.\n",
    ">>> except ExceptionII:\n",
    ">>>    If there is ExceptionII, then execute this block.\n",
    ">>>    ......................\n",
    ">>> else:\n",
    ">>>    If there is no exception then execute this block.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "With our **“Hello, World!”** program written, we are ready to run the program. We’ll use the python3 command along with the name of our program file. Let’s run the program:\n",
    "\n",
    "* A single try statement can have multiple except statements. This is useful when the **try block** contains statements that may throw different types of exceptions.\n",
    "\n",
    "* You can also provide a **generic except** clause, which handles any exception. \n",
    "\n",
    "* After the **except** clause(s), you can include an else-clause. The code in the else-block executes if the code in the try: block does not raise an exception. \n",
    "\n",
    "* The **else-block** is a good place for code that does not need the try: block's protection."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:22:56.773645Z",
     "start_time": "2021-10-18T18:22:56.763883Z"
    },
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Written content in the file successfully\n"
     ]
    }
   ],
   "source": [
    "# Example 1:\n",
    "\n",
    "try:\n",
    "    fh = open(\"testfile.txt\", \"w\")\n",
    "    fh.write(\"This is my test file for exception handling!!\")\n",
    "except IOError:\n",
    "    print (\"Error: can\\'t find file or read data\")\n",
    "else:\n",
    "    print (\"Written content in the file successfully\")\n",
    "    fh.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"img/eh1.png\" width=\"1000\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:23:10.926958Z",
     "start_time": "2021-10-18T18:23:02.681849Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Enter a number: 63\n",
      "Not an even number!\n"
     ]
    }
   ],
   "source": [
    "# Example 2: program to print the reciprocal of even numbers\n",
    "\n",
    "try:\n",
    "    num = int(input(\"Enter a number: \"))\n",
    "    assert num % 2 == 0\n",
    "except:\n",
    "    print(\"Not an even number!\")\n",
    "else:\n",
    "    reciprocal = 1/num\n",
    "    print(reciprocal)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Explanation**:\n",
    "\n",
    "If we pass an odd number:\n",
    "\n",
    "```python\n",
    "Enter a number: 1\n",
    "Not an even number!\n",
    "```\n",
    "\n",
    "If we pass an even number, the reciprocal is computed and displayed.\n",
    "\n",
    "```python\n",
    "Enter a number: 4\n",
    "0.25\n",
    "```\n",
    "\n",
    "However, if we pass 0, we get **`ZeroDivisionError`** as the code block inside else is not handled by preceding except.\n",
    "\n",
    "```python\n",
    "Enter a number: 0\n",
    "Traceback (most recent call last):\n",
    "  File \"<string>\", line 7, in <module>\n",
    "    reciprocal = 1/num\n",
    "ZeroDivisionError: division by zero\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The `except` Clause with No Exceptions\n",
    "\n",
    "You can also use the except statement with no exceptions defined as follows\n",
    "\n",
    "```python\n",
    ">>> try:\n",
    ">>>    You do your operations here\n",
    ">>>    ......................\n",
    ">>> except:\n",
    ">>>    If there is any exception, then execute this block.\n",
    ">>>    ......................\n",
    ">>> else:\n",
    ">>>    If there is no exception then execute this block\n",
    "```\n",
    "\n",
    "This kind of a **try-except** statement catches all the exceptions that occur. Using this kind of try-except statement is not considered a good programming practice though, because it catches all exceptions but does not make the programmer identify the root cause of the problem that may occur."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The `except` Clause with Multiple Exceptions\n",
    "\n",
    "You can also use the same except statement to handle multiple exceptions as follows −\n",
    "\n",
    "```python\n",
    ">>>try:\n",
    ">>>   You do your operations here\n",
    ">>>   ......................\n",
    ">>>except(Exception1[, Exception2[,...ExceptionN]]]):\n",
    ">>>   If there is any exception from the given exception list,\n",
    ">>>   then execute this block.\n",
    ">>>   ......................\n",
    ">>>else:\n",
    ">>>   If there is no exception then execute this block.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Python `try`-`finally` Clause\n",
    "\n",
    "The **`try`** statement in Python can have an optional **`finally`** clause. This clause is executed no matter what, and is generally used to release external resources.\n",
    "\n",
    "For example, we may be connected to a remote data center through the network or working with a file or a Graphical User Interface (GUI).\n",
    "\n",
    "In all these circumstances, we must clean up the resource before the program comes to a halt whether it successfully ran or not. These actions (closing a file, GUI or disconnecting from network) are performed in the **`finally`** clause to guarantee the execution.\n",
    "\n",
    "You can use a **finally: block** along with a try: block. The finally: block is a place to put any code that must execute, whether the **try-block** raised an exception or not. The syntax of the **try-finally** statement is this −\n",
    "\n",
    "```python\n",
    ">>> try:\n",
    ">>>    You do your operations here;\n",
    ">>>     ......................\n",
    ">>>    Due to any exception, this may be skipped.\n",
    ">>> finally:\n",
    ">>>    This would always be executed.\n",
    ">>>    ......................\n",
    "```\n",
    "\n",
    ">**Note**: You can provide except clause(s), or a finally clause, but not both. You cannot use else clause as well along with a **finally clause**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:23:19.191606Z",
     "start_time": "2021-10-18T18:23:19.177935Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Error: can't find file or read data\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    fh = open(\"testfile.txt\", \"w\")\n",
    "    fh.write(\"This is my test file for exception handling!!\")\n",
    "finally:\n",
    "    print (\"Error: can\\'t find file or read data\")\n",
    "    fh.close()\n",
    "    \n",
    "# If you do not have permission to open the file in writing mode, \n",
    "# then this will produce the following result −"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here is an example of **[file operations](https://github.com/milaan9/05_Python_Files/blob/main/001_Python_File_Input_Output.ipynb)** to illustrate this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:23:30.807811Z",
     "start_time": "2021-10-18T18:23:30.786329Z"
    }
   },
   "outputs": [],
   "source": [
    "try:\n",
    "    f = open(\"testfile.txt\",encoding = 'utf-8')\n",
    "    # perform file operations\n",
    "finally:\n",
    "    f.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Explanation**:\n",
    "\n",
    "This type of construct makes sure that the file is closed even if an exception occurs during the program execution."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the **# Example 5:** code above the output is going to be **`TypeError`**.\n",
    "Now, let's add an additional block:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:23:52.480166Z",
     "start_time": "2021-10-18T18:23:45.979682Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Enter your name: Milaan\n",
      "Year you born: 1926\n",
      "You are Milaan. And your age is 96.\n",
      "I usually run with the try block\n",
      "I alway run.\n"
     ]
    }
   ],
   "source": [
    "# Example:\n",
    "\n",
    "try:\n",
    "    name = input('Enter your name: ')\n",
    "    year_born = input('Year you born: ')\n",
    "    age = 2022 - int(year_born)\n",
    "    print(f'You are {name}. And your age is {age}.')\n",
    "except TypeError:\n",
    "    print('Type error occur')\n",
    "except ValueError:\n",
    "    print('Value error occur')\n",
    "except ZeroDivisionError:\n",
    "    print('zero division error occur')\n",
    "else:\n",
    "    print('I usually run with the try block')\n",
    "finally:\n",
    "    print('I alway run.')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It is also shorten the above code as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:24:01.871273Z",
     "start_time": "2021-10-18T18:23:56.755068Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Enter your name: Milaan\n",
      "Year you born: 1926\n",
      "You are Milaan. And your age is 96.\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    name = input('Enter your name: ')\n",
    "    year_born = input('Year you born: ')\n",
    "    age = 2022 - int(year_born)\n",
    "    print(f'You are {name}. And your age is {age}.')\n",
    "except Exception as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Argument of an Exception\n",
    "\n",
    "An exception can have an **argument**, which is a value that gives additional information about the problem. The contents of the argument vary by exception. You capture an exception's argument by supplying a variable in the except clause as follows −"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:24:05.502131Z",
     "start_time": "2021-10-18T18:24:05.484558Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The argument does not contain numbers\n",
      " invalid literal for int() with base 10: 'xyz'\n"
     ]
    }
   ],
   "source": [
    "# Define a function here.\n",
    "def temp_convert(var):\n",
    "    try:\n",
    "        return int(var)\n",
    "    except ValueError as Argument:\n",
    "        print (\"The argument does not contain numbers\\n\", Argument)\n",
    "\n",
    "# Call above function here.\n",
    "temp_convert(\"xyz\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Raising an Exception\n",
    "\n",
    "You can raise exceptions in several ways by using the **`raise`** statement. \n",
    "\n",
    "The general syntax for the **`raise`** statement is as follows:\n",
    "\n",
    "```python\n",
    "raise [Exception [, args [, traceback]]]\n",
    "```\n",
    "\n",
    "Here, Exception is the type of exception (for example, **NameError**) and argument is a value for the exception argument. The argument is optional; if not supplied, the exception argument is None.\n",
    "\n",
    "The final argument, traceback, is also optional (and rarely used in practice), and if present, is the traceback object used for the exception."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:24:08.211606Z",
     "start_time": "2021-10-18T18:24:08.203795Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "error in level argument -10\n"
     ]
    }
   ],
   "source": [
    "def functionName( level ):\n",
    "    if level < 1:\n",
    "        raise Exception(level)\n",
    "        # The code below to this would not be executed\n",
    "        # if we raise the exception\n",
    "    return level\n",
    "\n",
    "try:\n",
    "    l = functionName(-10)\n",
    "    print (\"level = \",l)\n",
    "except Exception as e:\n",
    "    print (\"error in level argument\",e.args[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Writing the “Hello, World!” Program\n",
    "\n",
    "Python also allows you to create your own exceptions by deriving classes from the standard **built-in exceptions**.\n",
    "\n",
    "Here is an example related to **RuntimeError**. Here, a class is created that is subclassed from RuntimeError. This is useful when you need to display more **specific information** when an exception is caught.\n",
    "\n",
    "In the try block, the user-defined exception is raised and caught in the except block. The variable e is used to create an instance of the class **Networkerror**.\n",
    "\n",
    "```python\n",
    ">>> class Networkerror(RuntimeError):\n",
    ">>>     def _init_(self, arg):\n",
    ">>>        self.args = arg\n",
    "```\n",
    "       \n",
    "So once you have defined the above class, you can raise the exception as follows −\n",
    "\n",
    "```python\n",
    ">>> try:\n",
    ">>>    raise Networkerror(\"Bad hostname\")\n",
    ">>> except Networkerror,e:\n",
    ">>>    print(e.args)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Packing and Unpacking Arguments in Python\n",
    "\n",
    "We use two operators:\n",
    "\n",
    "- **`*`** for tuples\n",
    "- __`**`__ for dictionaries\n",
    "\n",
    "Let us take as an example below. It takes only arguments but we have list. We can unpack the list and changes to argument."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Unpacking"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Unpacking Lists"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:24:11.762386Z",
     "start_time": "2021-10-18T18:24:11.602720Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "sum_of_five_nums() missing 4 required positional arguments: 'b', 'c', 'd', and 'e'",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-17-b951d4fef2c8>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m      3\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      4\u001b[0m \u001b[0mlst\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m3\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m4\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m5\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 5\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0msum_of_five_nums\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mlst\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;31m# TypeError: sum_of_five_nums() missing 4 required positional arguments: 'b', 'c', 'd', and 'e'\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mTypeError\u001b[0m: sum_of_five_nums() missing 4 required positional arguments: 'b', 'c', 'd', and 'e'"
     ]
    }
   ],
   "source": [
    "def sum_of_five_nums(a, b, c, d, e):\n",
    "    return a + b + c + d + e\n",
    "\n",
    "lst = [1, 2, 3, 4, 5]\n",
    "print(sum_of_five_nums(lst)) # TypeError: sum_of_five_nums() missing 4 required positional arguments: 'b', 'c', 'd', and 'e'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When we run the above code, it raises an error, because this function takes numbers (not a list) as arguments. Let us unpack/destructure the list."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:24:12.924982Z",
     "start_time": "2021-10-18T18:24:12.916194Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "15\n"
     ]
    }
   ],
   "source": [
    "def sum_of_five_nums(a, b, c, d, e):\n",
    "    return a + b + c + d + e\n",
    "\n",
    "lst = [1, 2, 3, 4, 5]\n",
    "print(sum_of_five_nums(*lst))  # 15"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also use unpacking in the **[range()](https://github.com/milaan9/04_Python_Functions/blob/main/002_Python_Functions_Built_in/053_Python_range%28%29.ipynb)** built-in function that expects a start and an end."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:24:13.972830Z",
     "start_time": "2021-10-18T18:24:13.954281Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[2, 3, 4, 5, 6]\n",
      "range(2, 7)\n"
     ]
    }
   ],
   "source": [
    "numbers = range(2, 7)  # normal call with separate arguments\n",
    "print(list(numbers)) # [2, 3, 4, 5, 6]\n",
    "args = [2, 7]\n",
    "numbers = range(*args)  # call with arguments unpacked from a list\n",
    "print(numbers)      # [2, 3, 4, 5,6]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Unpacking List or a Tuple"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:24:15.114433Z",
     "start_time": "2021-10-18T18:24:15.090999Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Finland Sweden Norway ['Denmark', 'Iceland']\n",
      "1 [2, 3, 4, 5, 6] 7\n"
     ]
    }
   ],
   "source": [
    "countries = ['Finland', 'Sweden', 'Norway', 'Denmark', 'Iceland']\n",
    "fin, sw, nor, *rest = countries\n",
    "print(fin, sw, nor, rest)   # Finland Sweden Norway ['Denmark', 'Iceland']\n",
    "numbers = [1, 2, 3, 4, 5, 6, 7]\n",
    "one, *middle, last = numbers\n",
    "print(one, middle, last)      #  1 [2, 3, 4, 5, 6] 7"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Unpacking Dictionaries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:24:16.053883Z",
     "start_time": "2021-10-18T18:24:16.034358Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Milaan lives in England, London. He is 96 year old.\n"
     ]
    }
   ],
   "source": [
    "def unpacking_person_info(name, country, city, age):\n",
    "    return f'{name} lives in {country}, {city}. He is {age} year old.'\n",
    "dct = {'name':'Milaan', 'country':'England', 'city':'London', 'age':96}\n",
    "print(unpacking_person_info(**dct)) # Milaan lives in England, London. He is 96 year old."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Packing\n",
    "\n",
    "Sometimes we never know how many arguments need to be passed to a python function. We can use the packing method to allow our function to take unlimited number or arbitrary number of arguments."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Packing Lists"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:24:17.345388Z",
     "start_time": "2021-10-18T18:24:17.331721Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "6\n",
      "28\n"
     ]
    }
   ],
   "source": [
    "def sum_all(*args):\n",
    "    s = 0\n",
    "    for i in args:\n",
    "        s += i\n",
    "    return s\n",
    "print(sum_all(1, 2, 3))             # 6\n",
    "print(sum_all(1, 2, 3, 4, 5, 6, 7)) # 28"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Packing Dictionaries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:24:17.991386Z",
     "start_time": "2021-10-18T18:24:17.982596Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{key} = {kwargs[key]}\n",
      "{key} = {kwargs[key]}\n",
      "{key} = {kwargs[key]}\n",
      "{key} = {kwargs[key]}\n",
      "{'name': 'Milaan', 'country': 'England', 'city': 'London', 'age': 96}\n"
     ]
    }
   ],
   "source": [
    "def packing_person_info(**kwargs):\n",
    "    # check the type of kwargs and it is a dict type\n",
    "    # print(type(kwargs))\n",
    "        # Printing dictionary items\n",
    "    for key in kwargs:\n",
    "        print(\"{key} = {kwargs[key]}\")\n",
    "    return kwargs\n",
    "\n",
    "print(packing_person_info(name=\"Milaan\",\n",
    "      country=\"England\", city=\"London\", age=96))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Spreading in Python\n",
    "\n",
    "Like in JavaScript, spreading is possible in Python. Let us check it in an example below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:24:18.884449Z",
     "start_time": "2021-10-18T18:24:18.867852Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 1, 2, 3, 4, 5, 6, 7]\n",
      "['Finland', 'Sweden', 'Norway', 'Denmark', 'Iceland']\n"
     ]
    }
   ],
   "source": [
    "lst_one = [1, 2, 3]\n",
    "lst_two = [4, 5, 6, 7]\n",
    "lst = [0, *lst_one, *lst_two]\n",
    "print(lst)          # [0, 1, 2, 3, 4, 5, 6, 7]\n",
    "country_lst_one = ['Finland', 'Sweden', 'Norway']\n",
    "country_lst_two = ['Denmark', 'Iceland']\n",
    "nordic_countries = [*country_lst_one, *country_lst_two]\n",
    "print(nordic_countries)  # ['Finland', 'Sweden', 'Norway', 'Denmark', 'Iceland']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Enumerate\n",
    "\n",
    "If we are interested in an index of a list, we use *enumerate* built-in function to get the index of each item in the list."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:24:20.685720Z",
     "start_time": "2021-10-18T18:24:20.674978Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0 20\n",
      "1 30\n",
      "2 40\n"
     ]
    }
   ],
   "source": [
    "for index, item in enumerate([20, 30, 40]):\n",
    "    print(index, item)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:24:21.672534Z",
     "start_time": "2021-10-18T18:24:21.651053Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hi\n",
      "The country {i} has been found at index {index}\n",
      "hi\n",
      "hi\n",
      "hi\n",
      "hi\n"
     ]
    }
   ],
   "source": [
    "for index, i in enumerate(countries):\n",
    "    print('hi')\n",
    "    if i == 'Finland':\n",
    "        print('The country {i} has been found at index {index}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Zip\n",
    "\n",
    "Sometimes we would like to combine lists when looping through them. See the example below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-10-18T18:24:23.259935Z",
     "start_time": "2021-10-18T18:24:23.243339Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[{'fruit': 'banana', 'veg': 'Tomato'}, {'fruit': 'orange', 'veg': 'Potato'}, {'fruit': 'mango', 'veg': 'Cabbage'}, {'fruit': 'lemon', 'veg': 'Onion'}, {'fruit': 'lime', 'veg': 'Carrot'}]\n"
     ]
    }
   ],
   "source": [
    "fruits = ['banana', 'orange', 'mango', 'lemon', 'lime']                    \n",
    "vegetables = ['Tomato', 'Potato', 'Cabbage','Onion', 'Carrot']\n",
    "fruits_and_veges = []\n",
    "for f, v in zip(fruits, vegetables):\n",
    "    fruits_and_veges.append({'fruit':f, 'veg':v})\n",
    "\n",
    "print(fruits_and_veges)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 💻 Exercises ➞ <span class='label label-default'>Exceptions Handling</span>\n",
    "\n",
    "### Exercises ➞ <span class='label label-default'>Level 1</span>\n",
    "\n",
    "1. names = ['Finland', 'Sweden', 'Norway','Denmark','Iceland', 'Estonia','Russia']. Unpack the first five countries and store them in a variable **`nordic_countries`**, store Estonia and Russia in es, and ru respectively."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "hide_input": false,
  "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.8.8"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "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": 2
}