<commit_before>{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# LBYL versus EAFP"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "In some other languages,\n",
    "one can not recover from an error,\n",
    "or it is difficult to recover from an error,\n",
    "so one tests input before doing something that could provoke the error.\n",
    "This technique is called **Look Before You Leap** (LBYL)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For example, one must avoid dividing by zero.\n",
    "\n",
    "Below is code that divides by numbers. When it gets the zero, it crashes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "numbers = (3, 1, 0, -1, -2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "foo(3) --> 3\n",
      "foo(1) --> 10\n"
     ]
    },
    {
     "ename": "ZeroDivisionError",
     "evalue": "integer division or modulo by zero",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mZeroDivisionError\u001b[0m                         Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-2-e422f1d4e230>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      4\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 5\u001b[0;31m     \u001b[0my\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfoo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      6\u001b[0m     \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"foo(%d) --> %d\"\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m<ipython-input-2-e422f1d4e230>\u001b[0m in \u001b[0;36mfoo\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mfoo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m     \u001b[0;32mreturn\u001b[0m \u001b[0;36m10\u001b[0m \u001b[0;34m//\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      4\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      5\u001b[0m     \u001b[0my\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfoo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mZeroDivisionError\u001b[0m: integer division or modulo by zero"
     ]
    }
   ],
   "source": [
    "def foo(x):\n",
    "    return 10 // x\n",
    "\n",
    "for x in numbers:\n",
    "    y = foo(x)\n",
    "    print(\"foo(%d) --> %d\" % (x, y))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So one checks before dividing as shown below.\n",
    "\n",
    "Checking before doing something is called \"**Look Before You Leap**\" (LBYL)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "foo(3) --> 3\n",
      "foo(1) --> 10\n",
      "foo(0) --> 0\n",
      "foo(-1) --> -10\n",
      "foo(-2) --> -5\n"
     ]
    }
   ],
   "source": [
    "def foo(x):\n",
    "    if x == 0:\n",
    "        y = 0\n",
    "    else:\n",
    "        y = 10 // x\n",
    "    return y\n",
    "\n",
    "for x in numbers:\n",
    "    y = foo(x)\n",
    "    print(\"foo(%d) --> %d\" % (x, y))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Another technique is to just try stuff,\n",
    "and if it blows up, do something else.\n",
    "\n",
    "This technique is called **Easier to Ask Forgiveness than Permission** (EAFP).\n",
    "\n",
    "Python makes it very easy to do something else\n",
    "when something blows up."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "foo(3) --> 3\n",
      "foo(1) --> 10\n",
      "foo(0) --> 0\n",
      "foo(-1) --> -10\n",
      "foo(-2) --> -5\n"
     ]
    }
   ],
   "source": [
    "def foo(x):\n",
    "    try:\n",
    "        y = 10 // x\n",
    "    except ZeroDivisionError:\n",
    "        y = 0\n",
    "    return y\n",
    "\n",
    "for x in numbers:\n",
    "    y = foo(x)\n",
    "    print(\"foo(%d) --> %d\" % (x, y))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For that simple example,\n",
    "EAFP does not have much if any benefit over LBYL.\n",
    "For that simple example,\n",
    "there is not much benefit in the size or readability of the code.\n",
    "However, for more complicated problems,\n",
    "EAFP lets one write much simpler and readable code.\n",
    "\n",
    "We will use the example of determining if a string is a valid float for Python.\n",
    "\n",
    "See\n",
    "[2.4.6. Floating point literals](https://docs.python.org/3/reference/lexical_analysis.html#floating-point-literals)\n",
    "for what constitutes a valid float.\n",
    "\n",
    "    floatnumber   ::=  pointfloat | exponentfloat\n",
    "    pointfloat    ::=  [digitpart] fraction | digitpart \".\"\n",
    "    exponentfloat ::=  (digitpart | pointfloat) exponent\n",
    "    digitpart     ::=  digit ([\"_\"] digit)*\n",
    "    fraction      ::=  \".\" digitpart\n",
    "    exponent      ::=  (\"e\" | \"E\") [\"+\" | \"-\"] digitpart\n",
    "\n",
    "Some code for that follows."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import re\n",
    "\n",
    "def is_float(s, debug=False):\n",
    "    digit = f'([0-9])'\n",
    "    digitpart = f'({digit}(_?{digit})*)'                           # digit ([\"_\"] digit)*\n",
    "    fraction = f'([.]{digitpart})'                                 # \".\" digitpart\n",
    "    pointfloat = f'(({digitpart}?{fraction}) | ({digitpart}[.]))'  # [digitpart] fraction | digitpart \".\"\n",
    "    exponent = f'([eE][-+]?{digitpart})'                           # (\"e\" | \"E\") [\"+\" | \"-\"] digitpart\n",
    "    exponentfloat = f'(({digitpart} | {pointfloat}) {exponent})'   # (digitpart | pointfloat) exponent\n",
    "    floatnumber = f'^({pointfloat} | {exponentfloat})$'            # pointfloat | exponentfloat\n",
    "    floatnumber = f'^[-+]?({pointfloat} | {exponentfloat} | {digitpart})$'  # allow signs and ints\n",
    "    \n",
    "    if debug:\n",
    "        regular_expressions = (\n",
    "            digit,\n",
    "            digitpart,\n",
    "            fraction,\n",
    "            pointfloat,\n",
    "            exponent,\n",
    "            exponentfloat,\n",
    "            floatnumber,\n",
    "        )\n",
    "        for s in regular_expressions:\n",
    "            print(repr(s))\n",
    "            # print(str(s))\n",
    "            \n",
    "    float_pattern = re.compile(floatnumber, re.VERBOSE)\n",
    "\n",
    "    return re.match(float_pattern, s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "floats = '''\n",
    "    2\n",
    "    0\n",
    "    -1\n",
    "    +17.\n",
    "    .\n",
    "    -.17\n",
    "    17e-3\n",
    "    -19.e-3\n",
    "    hello\n",
    "'''.split()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "'([0-9])'\n",
      "'(([0-9])(_?([0-9]))*)'\n",
      "'([.](([0-9])(_?([0-9]))*))'\n",
      "'(((([0-9])(_?([0-9]))*)?([.](([0-9])(_?([0-9]))*))) | ((([0-9])(_?([0-9]))*)[.]))'\n",
      "'([eE][-+]?(([0-9])(_?([0-9]))*))'\n",
      "'(((([0-9])(_?([0-9]))*) | (((([0-9])(_?([0-9]))*)?([.](([0-9])(_?([0-9]))*))) | ((([0-9])(_?([0-9]))*)[.]))) ([eE][-+]?(([0-9])(_?([0-9]))*)))'\n",
      "'^[-+]?((((([0-9])(_?([0-9]))*)?([.](([0-9])(_?([0-9]))*))) | ((([0-9])(_?([0-9]))*)[.])) | (((([0-9])(_?([0-9]))*) | (((([0-9])(_?([0-9]))*)?([.](([0-9])(_?([0-9]))*))) | ((([0-9])(_?([0-9]))*)[.]))) ([eE][-+]?(([0-9])(_?([0-9]))*))) | (([0-9])(_?([0-9]))*))$'\n",
      "'2' -> True\n",
      "'0' -> True\n",
      "'-1' -> True\n",
      "'+17.' -> True\n",
      "'.' -> False\n",
      "'-.17' -> True\n",
      "'17e-3' -> True\n",
      "'-19.e-3' -> True\n",
      "'hello' -> False\n"
     ]
    }
   ],
   "source": [
    "if True:\n",
    "    is_float('', debug=True)\n",
    "\n",
    "    for s in floats:\n",
    "        print(f'{s!r} -> {bool(is_float(s))}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "total is 17.828\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "\n",
    "def is_float(s, debug=False):\n",
    "    digit = f'([0-9])'\n",
    "    digitpart = f'({digit}(_?{digit})*)'                           # digit ([\"_\"] digit)*\n",
    "    fraction = f'([.]{digitpart})'                                 # \".\" digitpart\n",
    "    pointfloat = f'(({digitpart}?{fraction}) | ({digitpart}[.]))'  # [digitpart] fraction | digitpart \".\"\n",
    "    exponent = f'([eE][-+]?{digitpart})'                           # (\"e\" | \"E\") [\"+\" | \"-\"] digitpart\n",
    "    exponentfloat = f'(({digitpart} | {pointfloat}) {exponent})'   # (digitpart | pointfloat) exponent\n",
    "    floatnumber = f'^({pointfloat} | {exponentfloat})$'            # pointfloat | exponentfloat\n",
    "    floatnumber = f'^[-+]?({pointfloat} | {exponentfloat} | {digitpart})$'  # allow signs and ints\n",
    "    \n",
    "    float_pattern = re.compile(floatnumber, re.VERBOSE)\n",
    "\n",
    "    return re.match(float_pattern, s)\n",
    "    \n",
    "def safe_float(s, default=0.):\n",
    "    if is_float(s):\n",
    "        x = float(s)\n",
    "    else:\n",
    "        x = default\n",
    "    return x\n",
    "\n",
    "def main(lines):\n",
    "    total = sum(safe_float(line) for line in lines)\n",
    "    print(f'total is {total}')\n",
    "        \n",
    "main(floats)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we try EAFP technique below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "total is 17.828\n"
     ]
    }
   ],
   "source": [
    "def safe_float(s, default=0.):\n",
    "    try:\n",
    "        x = float(s)\n",
    "    except ValueError:\n",
    "        x = default\n",
    "    return x\n",
    "\n",
    "def main(lines):\n",
    "    total = sum(safe_float(line) for line in lines)\n",
    "    print(f'total is {total}')\n",
    "        \n",
    "main(floats)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The EAFP code is much much simpler.\n",
    "\n",
    "The LBYL version was very complicated.\n",
    "If there was a bug in the LBYL version, how would you find it?\n",
    "If you fixed it, how much confidence would you have that your fix is correct?\n",
    "How hard would it be to have test cases that covered all the edge cases?\n",
    "How much confidence would you have that the test cases were comprehensive?\n",
    "\n",
    "# EAFP makes code easier to read, simpler, and more reliable.\n",
    "\n",
    "# This is what makes try/except one of Python's superpowers!!!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "should:\n",
    "\n",
    "- always specify at least one exception\n",
    "- put as little as possible in the try clause\n",
    "\n",
    "Because the 20170327-except-*.py programs can lock up Jupyter notebook,\n",
    "run them outside of the notebook.\n",
    "The cell below shows what they look like,\n",
    "but does not run them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false,
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "###############################################################################\n",
      "20170327-except-0-bare.py\n",
      "\n",
      "#!/usr/bin/env python3\n",
      "\n",
      "'''This program has two try/except sins:\n",
      "1. too much stuff in try: clause\n",
      "2. bare except\n",
      "See https://www.python.org/dev/peps/pep-0008/#programming-recommendations.\n",
      "\n",
      "Because of the bare except, one can not get out of the program by typing ^C.\n",
      "\n",
      "For iter(partial(input, prompt), sentinel) idiom,\n",
      "see https://mail.python.org/pipermail/centraloh/2016-July/002895.html\n",
      "and http://nbviewer.jupyter.org/github/james-prior/cohpy/blob/master/20160708-dojo-user-input-loop-with-iter-partial-input-prompt-sentinel.ipynb\n",
      ".\n",
      "'''\n",
      "\n",
      "from functools import partial\n",
      "\n",
      "prompt = 'Please enter an integer: '\n",
      "while True:\n",
      "    try:\n",
      "        for i in map(int, iter(partial(input, prompt), None)):\n",
      "            print(f'Got {i}. That is a valid integer.')\n",
      "    except: # This catches all exceptions, including KeyboardInterrupt.\n",
      "        print('ERROR: That was not an integer. Try again.')\n",
      "\n",
      "\n",
      "###############################################################################\n",
      "20170327-except-1-valueerror.py\n",
      "\n",
      "#!/usr/bin/env python3\n",
      "\n",
      "'''This program has one try/except sin:\n",
      "1. too much stuff in try: clause\n",
      "See https://www.python.org/dev/peps/pep-0008/#programming-recommendations.\n",
      "\n",
      "Because the except is not bare and does not specify KeyboardInterrupt,\n",
      "one can get out of the program by typing ^C.\n",
      "'''\n",
      "\n",
      "from functools import partial\n",
      "\n",
      "prompt = 'Please enter an integer: '\n",
      "while True:\n",
      "    try:\n",
      "        for i in map(int, iter(partial(input, prompt), None)):\n",
      "            print(f'Got {i}. That is a valid integer.')\n",
      "    except ValueError:\n",
      "        print('ERROR: That was not an integer. Try again.')\n",
      "\n",
      "\n",
      "###############################################################################\n",
      "20170327-except-3-valueerror-min-try.py\n",
      "\n",
      "#!/usr/bin/env python3\n",
      "\n",
      "'''This program has no try/except sins.\n",
      "\n",
      "Because the except is not bare and does not specify KeyboardInterrupt,\n",
      "one can get out of the program by typing ^C.\n",
      "'''\n",
      "\n",
      "from functools import partial\n",
      "\n",
      "prompt = 'Please enter an integer: '\n",
      "for s in iter(partial(input, prompt), None):\n",
      "    try:\n",
      "        # This try clause has minimal code. That is good!\n",
      "        i = int(s)\n",
      "    except ValueError:\n",
      "        print('ERROR: That was not an integer. Try again.')\n",
      "    else:\n",
      "        print(f'Got {i}. That is a valid integer.')\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from glob import glob\n",
    "import os\n",
    "\n",
    "for filename in sorted(glob('20170327-except-*.py')):\n",
    "    print(79 * '#')\n",
    "    print(filename)\n",
    "    print()\n",
    "    with open(filename) as f:\n",
    "        print(f.read())\n",
    "    print()"
   ]
  }
 ],
 "metadata": {
  "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.6.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
<commit_msg>20170327-cohpy-lbyl-v-eafp.ipynb: Use f-strings.<commit_after>{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# LBYL versus EAFP"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "In some other languages,\n",
    "one can not recover from an error,\n",
    "or it is difficult to recover from an error,\n",
    "so one tests input before doing something that could provoke the error.\n",
    "This technique is called **Look Before You Leap** (LBYL)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For example, one must avoid dividing by zero.\n",
    "\n",
    "Below is code that divides by numbers. When it gets the zero, it crashes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "numbers = (3, 1, 0, -1, -2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "foo(3) --> 3\n",
      "foo(1) --> 10\n"
     ]
    },
    {
     "ename": "ZeroDivisionError",
     "evalue": "integer division or modulo by zero",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mZeroDivisionError\u001b[0m                         Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-2-e422f1d4e230>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      4\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 5\u001b[0;31m     \u001b[0my\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfoo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      6\u001b[0m     \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"foo(%d) --> %d\"\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m<ipython-input-2-e422f1d4e230>\u001b[0m in \u001b[0;36mfoo\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mfoo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m     \u001b[0;32mreturn\u001b[0m \u001b[0;36m10\u001b[0m \u001b[0;34m//\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      4\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      5\u001b[0m     \u001b[0my\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfoo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mZeroDivisionError\u001b[0m: integer division or modulo by zero"
     ]
    }
   ],
   "source": [
    "def foo(x):\n",
    "    return 10 // x\n",
    "\n",
    "for x in numbers:\n",
    "    y = foo(x)\n",
    "    print(f'foo({x}) --> {y}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So one checks before dividing as shown below.\n",
    "\n",
    "Checking before doing something is called \"**Look Before You Leap**\" (LBYL)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "foo(3) --> 3\n",
      "foo(1) --> 10\n",
      "foo(0) --> 0\n",
      "foo(-1) --> -10\n",
      "foo(-2) --> -5\n"
     ]
    }
   ],
   "source": [
    "def foo(x):\n",
    "    if x == 0:\n",
    "        y = 0\n",
    "    else:\n",
    "        y = 10 // x\n",
    "    return y\n",
    "\n",
    "for x in numbers:\n",
    "    y = foo(x)\n",
    "    print(f'foo({x}) --> {y}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Another technique is to just try stuff,\n",
    "and if it blows up, do something else.\n",
    "\n",
    "This technique is called **Easier to Ask Forgiveness than Permission** (EAFP).\n",
    "\n",
    "Python makes it very easy to do something else\n",
    "when something blows up."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "foo(3) --> 3\n",
      "foo(1) --> 10\n",
      "foo(0) --> 0\n",
      "foo(-1) --> -10\n",
      "foo(-2) --> -5\n"
     ]
    }
   ],
   "source": [
    "def foo(x):\n",
    "    try:\n",
    "        y = 10 // x\n",
    "    except ZeroDivisionError:\n",
    "        y = 0\n",
    "    return y\n",
    "\n",
    "for x in numbers:\n",
    "    y = foo(x)\n",
    "    print(f'foo({x}) --> {y}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For that simple example,\n",
    "EAFP does not have much if any benefit over LBYL.\n",
    "For that simple example,\n",
    "there is not much benefit in the size or readability of the code.\n",
    "However, for more complicated problems,\n",
    "EAFP lets one write much simpler and readable code.\n",
    "\n",
    "We will use the example of determining if a string is a valid float for Python.\n",
    "\n",
    "See\n",
    "[2.4.6. Floating point literals](https://docs.python.org/3/reference/lexical_analysis.html#floating-point-literals)\n",
    "for what constitutes a valid float.\n",
    "\n",
    "    floatnumber   ::=  pointfloat | exponentfloat\n",
    "    pointfloat    ::=  [digitpart] fraction | digitpart \".\"\n",
    "    exponentfloat ::=  (digitpart | pointfloat) exponent\n",
    "    digitpart     ::=  digit ([\"_\"] digit)*\n",
    "    fraction      ::=  \".\" digitpart\n",
    "    exponent      ::=  (\"e\" | \"E\") [\"+\" | \"-\"] digitpart\n",
    "\n",
    "Some code for that follows."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import re\n",
    "\n",
    "def is_float(s, debug=False):\n",
    "    digit = f'([0-9])'\n",
    "    digitpart = f'({digit}(_?{digit})*)'                           # digit ([\"_\"] digit)*\n",
    "    fraction = f'([.]{digitpart})'                                 # \".\" digitpart\n",
    "    pointfloat = f'(({digitpart}?{fraction}) | ({digitpart}[.]))'  # [digitpart] fraction | digitpart \".\"\n",
    "    exponent = f'([eE][-+]?{digitpart})'                           # (\"e\" | \"E\") [\"+\" | \"-\"] digitpart\n",
    "    exponentfloat = f'(({digitpart} | {pointfloat}) {exponent})'   # (digitpart | pointfloat) exponent\n",
    "    floatnumber = f'^({pointfloat} | {exponentfloat})$'            # pointfloat | exponentfloat\n",
    "    floatnumber = f'^[-+]?({pointfloat} | {exponentfloat} | {digitpart})$'  # allow signs and ints\n",
    "    \n",
    "    if debug:\n",
    "        regular_expressions = (\n",
    "            digit,\n",
    "            digitpart,\n",
    "            fraction,\n",
    "            pointfloat,\n",
    "            exponent,\n",
    "            exponentfloat,\n",
    "            floatnumber,\n",
    "        )\n",
    "        for s in regular_expressions:\n",
    "            print(repr(s))\n",
    "            # print(str(s))\n",
    "            \n",
    "    float_pattern = re.compile(floatnumber, re.VERBOSE)\n",
    "\n",
    "    return re.match(float_pattern, s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "floats = '''\n",
    "    2\n",
    "    0\n",
    "    -1\n",
    "    +17.\n",
    "    .\n",
    "    -.17\n",
    "    17e-3\n",
    "    -19.e-3\n",
    "    hello\n",
    "'''.split()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "'([0-9])'\n",
      "'(([0-9])(_?([0-9]))*)'\n",
      "'([.](([0-9])(_?([0-9]))*))'\n",
      "'(((([0-9])(_?([0-9]))*)?([.](([0-9])(_?([0-9]))*))) | ((([0-9])(_?([0-9]))*)[.]))'\n",
      "'([eE][-+]?(([0-9])(_?([0-9]))*))'\n",
      "'(((([0-9])(_?([0-9]))*) | (((([0-9])(_?([0-9]))*)?([.](([0-9])(_?([0-9]))*))) | ((([0-9])(_?([0-9]))*)[.]))) ([eE][-+]?(([0-9])(_?([0-9]))*)))'\n",
      "'^[-+]?((((([0-9])(_?([0-9]))*)?([.](([0-9])(_?([0-9]))*))) | ((([0-9])(_?([0-9]))*)[.])) | (((([0-9])(_?([0-9]))*) | (((([0-9])(_?([0-9]))*)?([.](([0-9])(_?([0-9]))*))) | ((([0-9])(_?([0-9]))*)[.]))) ([eE][-+]?(([0-9])(_?([0-9]))*))) | (([0-9])(_?([0-9]))*))$'\n",
      "'2' -> True\n",
      "'0' -> True\n",
      "'-1' -> True\n",
      "'+17.' -> True\n",
      "'.' -> False\n",
      "'-.17' -> True\n",
      "'17e-3' -> True\n",
      "'-19.e-3' -> True\n",
      "'hello' -> False\n"
     ]
    }
   ],
   "source": [
    "if True:\n",
    "    is_float('', debug=True)\n",
    "\n",
    "    for s in floats:\n",
    "        print(f'{s!r} -> {bool(is_float(s))}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "total is 17.828\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "\n",
    "def is_float(s, debug=False):\n",
    "    digit = f'([0-9])'\n",
    "    digitpart = f'({digit}(_?{digit})*)'                           # digit ([\"_\"] digit)*\n",
    "    fraction = f'([.]{digitpart})'                                 # \".\" digitpart\n",
    "    pointfloat = f'(({digitpart}?{fraction}) | ({digitpart}[.]))'  # [digitpart] fraction | digitpart \".\"\n",
    "    exponent = f'([eE][-+]?{digitpart})'                           # (\"e\" | \"E\") [\"+\" | \"-\"] digitpart\n",
    "    exponentfloat = f'(({digitpart} | {pointfloat}) {exponent})'   # (digitpart | pointfloat) exponent\n",
    "    floatnumber = f'^({pointfloat} | {exponentfloat})$'            # pointfloat | exponentfloat\n",
    "    floatnumber = f'^[-+]?({pointfloat} | {exponentfloat} | {digitpart})$'  # allow signs and ints\n",
    "    \n",
    "    float_pattern = re.compile(floatnumber, re.VERBOSE)\n",
    "\n",
    "    return re.match(float_pattern, s)\n",
    "    \n",
    "def safe_float(s, default=0.):\n",
    "    if is_float(s):\n",
    "        x = float(s)\n",
    "    else:\n",
    "        x = default\n",
    "    return x\n",
    "\n",
    "def main(lines):\n",
    "    total = sum(safe_float(line) for line in lines)\n",
    "    print(f'total is {total}')\n",
    "        \n",
    "main(floats)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we try EAFP technique below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "total is 17.828\n"
     ]
    }
   ],
   "source": [
    "def safe_float(s, default=0.):\n",
    "    try:\n",
    "        x = float(s)\n",
    "    except ValueError:\n",
    "        x = default\n",
    "    return x\n",
    "\n",
    "def main(lines):\n",
    "    total = sum(safe_float(line) for line in lines)\n",
    "    print(f'total is {total}')\n",
    "        \n",
    "main(floats)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The EAFP code is much much simpler.\n",
    "\n",
    "The LBYL version was very complicated.\n",
    "If there was a bug in the LBYL version, how would you find it?\n",
    "If you fixed it, how much confidence would you have that your fix is correct?\n",
    "How hard would it be to have test cases that covered all the edge cases?\n",
    "How much confidence would you have that the test cases were comprehensive?\n",
    "\n",
    "# EAFP makes code easier to read, simpler, and more reliable.\n",
    "\n",
    "# This is what makes try/except one of Python's superpowers!!!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "should:\n",
    "\n",
    "- always specify at least one exception\n",
    "- put as little as possible in the try clause\n",
    "\n",
    "Because the 20170327-except-*.py programs can lock up Jupyter notebook,\n",
    "run them outside of the notebook.\n",
    "The cell below shows what they look like,\n",
    "but does not run them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false,
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "###############################################################################\n",
      "20170327-except-0-bare.py\n",
      "\n",
      "#!/usr/bin/env python3\n",
      "\n",
      "'''This program has two try/except sins:\n",
      "1. too much stuff in try: clause\n",
      "2. bare except\n",
      "See https://www.python.org/dev/peps/pep-0008/#programming-recommendations.\n",
      "\n",
      "Because of the bare except, one can not get out of the program by typing ^C.\n",
      "\n",
      "For iter(partial(input, prompt), sentinel) idiom,\n",
      "see https://mail.python.org/pipermail/centraloh/2016-July/002895.html\n",
      "and http://nbviewer.jupyter.org/github/james-prior/cohpy/blob/master/20160708-dojo-user-input-loop-with-iter-partial-input-prompt-sentinel.ipynb\n",
      ".\n",
      "'''\n",
      "\n",
      "from functools import partial\n",
      "\n",
      "prompt = 'Please enter an integer: '\n",
      "while True:\n",
      "    try:\n",
      "        for i in map(int, iter(partial(input, prompt), None)):\n",
      "            print(f'Got {i}. That is a valid integer.')\n",
      "    except: # This catches all exceptions, including KeyboardInterrupt.\n",
      "        print('ERROR: That was not an integer. Try again.')\n",
      "\n",
      "\n",
      "###############################################################################\n",
      "20170327-except-1-valueerror.py\n",
      "\n",
      "#!/usr/bin/env python3\n",
      "\n",
      "'''This program has one try/except sin:\n",
      "1. too much stuff in try: clause\n",
      "See https://www.python.org/dev/peps/pep-0008/#programming-recommendations.\n",
      "\n",
      "Because the except is not bare and does not specify KeyboardInterrupt,\n",
      "one can get out of the program by typing ^C.\n",
      "'''\n",
      "\n",
      "from functools import partial\n",
      "\n",
      "prompt = 'Please enter an integer: '\n",
      "while True:\n",
      "    try:\n",
      "        for i in map(int, iter(partial(input, prompt), None)):\n",
      "            print(f'Got {i}. That is a valid integer.')\n",
      "    except ValueError:\n",
      "        print('ERROR: That was not an integer. Try again.')\n",
      "\n",
      "\n",
      "###############################################################################\n",
      "20170327-except-3-valueerror-min-try.py\n",
      "\n",
      "#!/usr/bin/env python3\n",
      "\n",
      "'''This program has no try/except sins.\n",
      "\n",
      "Because the except is not bare and does not specify KeyboardInterrupt,\n",
      "one can get out of the program by typing ^C.\n",
      "'''\n",
      "\n",
      "from functools import partial\n",
      "\n",
      "prompt = 'Please enter an integer: '\n",
      "for s in iter(partial(input, prompt), None):\n",
      "    try:\n",
      "        # This try clause has minimal code. That is good!\n",
      "        i = int(s)\n",
      "    except ValueError:\n",
      "        print('ERROR: That was not an integer. Try again.')\n",
      "    else:\n",
      "        print(f'Got {i}. That is a valid integer.')\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from glob import glob\n",
    "import os\n",
    "\n",
    "for filename in sorted(glob('20170327-except-*.py')):\n",
    "    print(79 * '#')\n",
    "    print(filename)\n",
    "    print()\n",
    "    with open(filename) as f:\n",
    "        print(f.read())\n",
    "    print()"
   ]
  }
 ],
 "metadata": {
  "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.6.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}

{'fraction_non_alphanumeric': 0.2458945424110364, 'fraction_numerical': 0.05766400184709516, 'mean_word_length': 2.1073446327683616, 'pattern_counts': {'":': 268, '<': 9, '<?xml version=': 0, '>': 65, 'https://': 8, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6865145', 'n_tokens_mistral': 15049, 'n_tokens_neox': 13903, 'n_words': 2536}