Skip to content
Snippets Groups Projects
session_2_defensive_programming.ipynb 14.4 KiB
Newer Older
karcher's avatar
karcher committed
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Defensive Programming\n",
    "\n",
    "Defensive programming describes concepts and approaches we can use to ensure that...\n",
    "* Code does what we think it does (i.e. is free of bugs)\n",
    "* Our programs fail when they should...\n",
    "* ... and do so with informative error messages (throwback to last session)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's say we want a function that takes in a list and computes the mean of all elements in that list"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Assert statement\n",
    "\n",
    "The simplest tool we have to ensure that our functions are doing what we intend them to do are assert statements"
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
karcher's avatar
karcher committed
   "metadata": {},
   "outputs": [],
   "source": [
karcher's avatar
karcher committed
    "def get_sum_of_numbers(alist):\n",
karcher's avatar
karcher committed
    "    tmp = 0\n",
    "    for element in alist:\n",
karcher's avatar
karcher committed
    "        tmp = tmp + element\n",
    "    return(tmp)"
karcher's avatar
karcher committed
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
   "metadata": {},
   "outputs": [],
karcher's avatar
karcher committed
   "source": [
karcher's avatar
karcher committed
    "get_sum_of_numbers([1,2,3,4,5])"
karcher's avatar
karcher committed
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
   "metadata": {},
   "outputs": [],
karcher's avatar
karcher committed
   "source": [
karcher's avatar
karcher committed
    "get_sum_of_numbers([\"a\", \"b\", \"c\", \"d\", \"e\"])"
karcher's avatar
karcher committed
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
karcher's avatar
karcher committed
   "metadata": {},
   "outputs": [],
   "source": [
karcher's avatar
karcher committed
    "def get_sum_of_numbers(alist):\n",
    "    assert all([isinstance(element, int) for element in alist]), \"Not all elements are integers\"\n",
karcher's avatar
karcher committed
    "    tmp = 0\n",
    "    for element in alist:\n",
karcher's avatar
karcher committed
    "        tmp = tmp + element\n",
    "    return(tmp)"
karcher's avatar
karcher committed
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
   "metadata": {},
   "outputs": [],
karcher's avatar
karcher committed
   "source": [
karcher's avatar
karcher committed
    "get_sum_of_numbers([1,2,3,4,5])"
karcher's avatar
karcher committed
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
   "metadata": {},
   "outputs": [],
karcher's avatar
karcher committed
   "source": [
karcher's avatar
karcher committed
    "get_sum_of_numbers([\"a\", \"b\", \"c\", \"d\", \"e\"])"
karcher's avatar
karcher committed
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
   "metadata": {},
   "outputs": [],
karcher's avatar
karcher committed
   "source": [
karcher's avatar
karcher committed
    "get_sum_of_numbers([1,2,3,4,5.01])"
karcher's avatar
karcher committed
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
karcher's avatar
karcher committed
   "metadata": {},
   "outputs": [],
   "source": [
    "import numbers\n",
    "def get_sum_of_numbers(alist):\n",
karcher's avatar
karcher committed
    "    assert all([isinstance(element, numbers.Number) for element in alist]), \"Not all elements are Numbers\"\n",
karcher's avatar
karcher committed
    "    tmp = 0\n",
    "    for element in alist:\n",
karcher's avatar
karcher committed
    "        tmp = tmp + element\n",
    "    return(tmp)"
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
   "metadata": {},
   "outputs": [],
karcher's avatar
karcher committed
   "source": [
karcher's avatar
karcher committed
    "get_sum_of_numbers([1,2,3,4,5.01])"
karcher's avatar
karcher committed
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
   "metadata": {},
   "outputs": [],
karcher's avatar
karcher committed
   "source": [
karcher's avatar
karcher committed
    "get_sum_of_numbers([])"
karcher's avatar
karcher committed
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
karcher's avatar
karcher committed
   "metadata": {},
   "outputs": [],
   "source": [
karcher's avatar
karcher committed
    "import numbers\n",
karcher's avatar
karcher committed
    "def get_sum_of_numbers(alist):\n",
karcher's avatar
karcher committed
    "    assert len(alist) > 0, \"List is empty\"\n",
    "    assert all([isinstance(element, numbers.Number) for element in alist]), \"Not all elements are Numbers\"\n",
karcher's avatar
karcher committed
    "    tmp = 0\n",
    "    for element in alist:\n",
karcher's avatar
karcher committed
    "        tmp = tmp + element\n",
    "    return(tmp)"
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
   "metadata": {},
   "outputs": [],
karcher's avatar
karcher committed
   "source": [
karcher's avatar
karcher committed
    "get_sum_of_numbers([])"
karcher's avatar
karcher committed
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Preconditions, postconditions and invariants\n",
    "\n",
    "Assertions can be grouped into:\n",
    "* Preconditions, which should be true the computional part of a function\n",
    "* Postconditions, which should be true afterwards\n",
    "* (Invariants, which should be true anywhere)"
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
karcher's avatar
karcher committed
   "metadata": {},
   "outputs": [],
   "source": [
karcher's avatar
karcher committed
    "import numbers\n",
    "def get_sum_of_numbers(alist):\n",
karcher's avatar
karcher committed
    "    # Preconditions\n",
karcher's avatar
karcher committed
    "    assert len(alist) > 0, \"List is empty\"\n",
    "    assert all([isinstance(element, numbers.Number) for element in alist]), \"Not all elements are Numbers\"\n",
karcher's avatar
karcher committed
    "    tmp = 0\n",
    "    for element in alist:\n",
karcher's avatar
karcher committed
    "        tmp = tmp + element\n",
    "    return(tmp)"
karcher's avatar
karcher committed
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
   "metadata": {},
   "outputs": [],
karcher's avatar
karcher committed
   "source": [
karcher's avatar
karcher committed
    "get_sum_of_numbers([1,2,3,4,5])"
karcher's avatar
karcher committed
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
karcher's avatar
karcher committed
   "metadata": {},
   "outputs": [],
   "source": [
karcher's avatar
karcher committed
    "import numbers\n",
    "def get_sum_of_numbers(alist):\n",
karcher's avatar
karcher committed
    "    # Preconditions\n",
karcher's avatar
karcher committed
    "    assert len(alist) > 0, \"List is empty\"\n",
    "    assert all([isinstance(element, numbers.Number) for element in alist]), \"Not all elements are Numbers\"\n",
karcher's avatar
karcher committed
    "    tmp = 0\n",
    "    for element in alist:\n",
karcher's avatar
karcher committed
    "        tmp = tmp + element\n",
    "    tmp = 'Your code has been visited by an evil spirit!'\n",
    "    return(tmp)"
karcher's avatar
karcher committed
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
   "metadata": {},
   "outputs": [],
karcher's avatar
karcher committed
   "source": [
karcher's avatar
karcher committed
    "get_sum_of_numbers([1, 2, 3])"
karcher's avatar
karcher committed
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
karcher's avatar
karcher committed
   "metadata": {},
   "outputs": [],
   "source": [
karcher's avatar
karcher committed
    "import numbers\n",
    "def get_sum_of_numbers(alist):\n",
karcher's avatar
karcher committed
    "    # Preconditions\n",
karcher's avatar
karcher committed
    "    assert len(alist) > 0, \"List is empty\"\n",
    "    assert all([isinstance(element, numbers.Number) for element in alist]), \"Not all elements are Numbers\"\n",
karcher's avatar
karcher committed
    "    tmp = 0\n",
    "    for element in alist:\n",
karcher's avatar
karcher committed
    "        tmp = tmp + element\n",
    "    tmp = 'Your code has been visited by an evil spirit!'\n",
karcher's avatar
karcher committed
    "    # Postcondition\n",
    "    assert isinstance(tmp, numbers.Number), \"Your sum isn't numerical...\"\n",
karcher's avatar
karcher committed
    "    return(tmp)"
karcher's avatar
karcher committed
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
   "metadata": {},
   "outputs": [],
karcher's avatar
karcher committed
   "source": [
karcher's avatar
karcher committed
    "get_sum_of_numbers([1, 2, 3])"
karcher's avatar
karcher committed
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Test-Driven Development\n",
    "\n",
    "Some programmers write code and then test that code somehow to be confident enough that it's bug free. \n",
    "\n",
    "Other programms create tests and then write code that has to pass these tests in an iterative manner. This approach is called test-driven development. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We want to write a function that calculates the largest overlap between numerical ranges. Each numerical range is a two-element tuple specifying the upper and lower boundaries."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![caption](python-overlapping-ranges.png)"
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
   "metadata": {},
   "outputs": [],
karcher's avatar
karcher committed
   "source": [
    "# Single range\n",
    "assert range_overlap([ (0.0, 1.0) ]) == (0.0, 1.0)\n",
    "# Two overlapping ranges\n",
    "assert range_overlap([ (2.0, 3.0), (2.0, 4.0) ]) == (2.0, 3.0)\n",
    "# Three overlapping ranges\n",
    "assert range_overlap([ (0.0, 1.0), (0.0, 2.0), (-1.0, 1.0) ]) == (0.0, 1.0)\n",
    "\n",
    "# What else is missing?\n",
    "# Twice the same range\n",
    "assert range_overlap([(0, 2), (0, 2)]) == (0.0, 2.0)\n",
    "# Two non-overlapping ranges. What is the desired output? (0, 0)? None?\n",
    "assert range_overlap([ (0.1, 1.0), (3.0, 4.0) ]) == None\n",
    "# More than two non-overlapping ranges. See above.\n",
    "assert range_overlap([ (0.1, 1.0), (3.0, 4.0), (10, 12) ]) == None\n",
karcher's avatar
karcher committed
    "# Two intervals touching\n",
karcher's avatar
karcher committed
    "assert range_overlap([(0, 2), (2, 4)]) == None\n",
    "# empty input?\n",
    "assert range_overlap([]) == None"
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
karcher's avatar
karcher committed
   "metadata": {},
   "outputs": [],
   "source": [
    "def range_overlap(ranges):\n",
    "    max_left = ranges[0][0]\n",
    "    min_right = ranges[0][1]\n",
    "    # ranges[2:] returns an empty list if len(ranges) == 1 and nothing is being looped over.\n",
    "    for (left, right) in ranges[1:]:\n",
    "        max_left = max(max_left, left)\n",
    "        min_right = min(min_right, right)\n",
    "    return (max_left, min_right)"
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
   "metadata": {},
   "outputs": [],
karcher's avatar
karcher committed
   "source": [
    "# Single range\n",
    "assert range_overlap([ (0.0, 1.0) ]) == (0.0, 1.0)\n",
    "# Two overlapping ranges\n",
    "assert range_overlap([ (2.0, 3.0), (2.0, 4.0) ]) == (2.0, 3.0)\n",
    "# Three overlapping ranges\n",
    "assert range_overlap([ (0.0, 1.0), (0.0, 2.0), (-1.0, 1.0) ]) == (0.0, 1.0)\n",
    "\n",
    "# What else is missing?\n",
    "# Twice the same range\n",
    "assert range_overlap([(0, 2), (0, 2)]) == (0.0, 2.0)\n",
    "# Two non-overlapping ranges. What is the desired output? (0, 0)? None?\n",
    "assert range_overlap([ (0.1, 1.0), (3.0, 4.0) ]) == None\n",
    "# More than two non-overlapping ranges. See above.\n",
    "assert range_overlap([ (0.1, 1.0), (3.0, 4.0), (10, 12) ]) == None\n",
    "# Two touching intervals\n",
    "assert range_overlap([(0, 2), (2, 4)]) == None\n",
    "# empty input?\n",
    "assert range_overlap([]) == None"
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
karcher's avatar
karcher committed
   "metadata": {},
   "outputs": [],
   "source": [
    "def range_overlap(ranges):\n",
    "    max_left = ranges[0][0]\n",
    "    min_right = ranges[0][1]\n",
    "    # ranges[2:] returns an empty list if len(ranges) == 0 and nothing is being looped over.\n",
    "    for (left, right) in ranges[1:]:\n",
    "        max_left = max(max_left, left)\n",
    "        min_right = min(min_right, right)\n",
    "    if max_left > min_right:\n",
    "        return(None)\n",
    "    return (max_left, min_right)"
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
   "metadata": {},
   "outputs": [],
karcher's avatar
karcher committed
   "source": [
    "# Single range\n",
    "assert range_overlap([ (0.0, 1.0) ]) == (0.0, 1.0)\n",
    "# Two overlapping ranges\n",
    "assert range_overlap([ (2.0, 3.0), (2.0, 4.0) ]) == (2.0, 3.0)\n",
    "# Three overlapping ranges\n",
    "assert range_overlap([ (0.0, 1.0), (0.0, 2.0), (-1.0, 1.0) ]) == (0.0, 1.0)\n",
    "\n",
    "# What else is missing?\n",
    "# Twice the same range\n",
    "assert range_overlap([(0, 2), (0, 2)]) == (0.0, 2.0)\n",
    "# Two non-overlapping ranges. What is the desired output? (0, 0)? None?\n",
    "assert range_overlap([ (0.1, 1.0), (3.0, 4.0) ]) == None\n",
    "# More than two non-overlapping ranges. See above.\n",
    "assert range_overlap([ (0.1, 1.0), (3.0, 4.0), (10, 12) ]) == None\n",
    "# Two touching intervals\n",
    "assert range_overlap([(0, 2), (2, 4)]) == None\n",
    "# empty input?\n",
    "assert range_overlap([]) == None"
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
karcher's avatar
karcher committed
   "metadata": {},
   "outputs": [],
   "source": [
    "def range_overlap(ranges):\n",
    "    max_left = ranges[0][0]\n",
    "    min_right = ranges[0][1]\n",
    "    # ranges[2:] returns an empty list if len(ranges) == 1 and nothing is being looped over.\n",
    "    for (left, right) in ranges[1:]:\n",
    "        max_left = max(max_left, left)\n",
    "        min_right = min(min_right, right)\n",
    "    if max_left >= min_right:\n",
    "        return(None)\n",
    "    return (max_left, min_right)"
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
   "metadata": {},
   "outputs": [],
karcher's avatar
karcher committed
   "source": [
    "# Single range\n",
    "assert range_overlap([ (0.0, 1.0) ]) == (0.0, 1.0)\n",
    "# Two overlapping ranges\n",
    "assert range_overlap([ (2.0, 3.0), (2.0, 4.0) ]) == (2.0, 3.0)\n",
    "# Three overlapping ranges\n",
    "assert range_overlap([ (0.0, 1.0), (0.0, 2.0), (-1.0, 1.0) ]) == (0.0, 1.0)\n",
    "\n",
    "# What else is missing?\n",
    "# Twice the same range\n",
    "assert range_overlap([(0, 2), (0, 2)]) == (0.0, 2.0)\n",
    "# Two non-overlapping ranges. What is the desired output? (0, 0)? None?\n",
    "assert range_overlap([ (0.1, 1.0), (3.0, 4.0) ]) == None\n",
    "# More than two non-overlapping ranges. See above.\n",
    "assert range_overlap([ (0.1, 1.0), (3.0, 4.0), (10, 12) ]) == None\n",
    "# Two touching intervals\n",
    "assert range_overlap([(0, 2), (2, 4)]) == None\n",
    "# empty input?\n",
    "assert range_overlap([]) == None"
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
karcher's avatar
karcher committed
   "metadata": {},
   "outputs": [],
   "source": [
    "def range_overlap(ranges):\n",
    "    if len(ranges) == 0:\n",
    "        return(None)\n",
    "    max_left = ranges[0][0]\n",
    "    min_right = ranges[0][1]\n",
    "    # ranges[2:] returns an empty list if len(ranges) == 1 and nothing is being looped over.\n",
    "    for (left, right) in ranges[1:]:\n",
    "        max_left = max(max_left, left)\n",
    "        min_right = min(min_right, right)\n",
    "    if max_left >= min_right:\n",
    "        return(None)\n",
    "    return (max_left, min_right)"
   ]
  },
  {
   "cell_type": "code",
karcher's avatar
karcher committed
   "execution_count": null,
karcher's avatar
karcher committed
   "metadata": {},
   "outputs": [],
   "source": [
    "# Single range\n",
    "assert range_overlap([ (0.0, 1.0) ]) == (0.0, 1.0)\n",
    "# Two overlapping ranges\n",
    "assert range_overlap([ (2.0, 3.0), (2.0, 4.0) ]) == (2.0, 3.0)\n",
    "# Three overlapping ranges\n",
    "assert range_overlap([ (0.0, 1.0), (0.0, 2.0), (-1.0, 1.0) ]) == (0.0, 1.0)\n",
    "\n",
    "# What else is missing?\n",
    "# Twice the same range\n",
    "assert range_overlap([(0, 2), (0, 2)]) == (0.0, 2.0)\n",
    "# Two non-overlapping ranges. What is the desired output? (0, 0)? None?\n",
    "assert range_overlap([ (0.1, 1.0), (3.0, 4.0) ]) == None\n",
    "# More than two non-overlapping ranges. See above.\n",
    "assert range_overlap([ (0.1, 1.0), (3.0, 4.0), (10, 12) ]) == None\n",
    "# Two touching intervals\n",
    "assert range_overlap([(0, 2), (2, 4)]) == None\n",
    "# empty input?\n",
    "assert range_overlap([]) == None"
   ]
  }
 ],
 "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.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}