{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Objects\n",
    "\n",
    "In the last session you learned how to package up useful code into functions. This is a really useful idea, as it lets you re-use useful code in your own scripts, and to then share useful code with other people.\n",
    "\n",
    "However, it is normal for functions to rely on data. For example, consider the Morse code `encode` and `decode` functions in the last lesson. These only work because of the data contained in the `letter_to_morse` dictionary. The functions would break if anyone changes the data in this dictionary."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "letter_to_morse = {'a':'.-', 'b':'-...', 'c':'-.-.', 'd':'-..', 'e':'.', 'f':'..-.',\n",
    "                   'g':'--.', 'h':'....', 'i':'..', 'j':'.---', 'k':'-.-', 'l':'.-..', 'm':'--',\n",
    "                   'n':'-.', 'o':'---', 'p':'.--.', 'q':'--.-', 'r':'.-.', 's':'...', 't':'-',\n",
    "                   'u':'..-', 'v':'...-', 'w':'.--', 'x':'-..-', 'y':'-.--', 'z':'--..',\n",
    "                   '0':'-----', '1':'.----', '2':'..---', '3':'...--', '4':'....-',\n",
    "                   '5':'.....', '6':'-....', '7':'--...', '8':'---..', '9':'----.',\n",
    "                   ' ':'/' }"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def encode(message):\n",
    "    morse = []\n",
    "    for letter in message:\n",
    "        morse.append( letter_to_morse[letter.lower()] )\n",
    "    return morse"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "encode(\"Hello\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The above `encode(\"Hello\")` has worked. However, if we change the data in `letter_to_morse`, e.g. swapping `l` from `.-..` to `-.--`, then we get `['....', '.', '-.--', '-.--', '---']`, which is wrong. We can make even larger changes, which would completely break the function..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "While such changes are easy to spot in this example, they become more difficult to find in larger programs. In addition, as you share code, you will find that people using your code will do weird things to the data on which it depends, which can introduce weird bugs and problems.\n",
    "\n",
    "The solution is to package a function together with the data on which it depends into a single object. This idea is the foundation of object orientated programming. To explore this, let us start with a simple example that packages the `encode` function together with the `letter_to_morse` dictionary on which it depends."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class Morse:\n",
    "    def __init__(self):\n",
    "        self._letter_to_morse = {'a':'.-', 'b':'-...', 'c':'-.-.', 'd':'-..', 'e':'.', 'f':'..-.',\n",
    "                   'g':'--.', 'h':'....', 'i':'..', 'j':'.---', 'k':'-.-', 'l':'.-..', 'm':'--',\n",
    "                   'n':'-.', 'o':'---', 'p':'.--.', 'q':'--.-', 'r':'.-.', 's':'...', 't':'-',\n",
    "                   'u':'..-', 'v':'...-', 'w':'.--', 'x':'-..-', 'y':'-.--', 'z':'--..',\n",
    "                   '0':'-----', '1':'.----', '2':'..---', '3':'...--', '4':'....-',\n",
    "                   '5':'.....', '6':'-....', '7':'--...', '8':'---..', '9':'----.',\n",
    "                   ' ':'/' }\n",
    "        \n",
    "    def encode(self, message):\n",
    "        morse = []\n",
    "        for letter in message:\n",
    "            morse.append( self._letter_to_morse[letter.lower()] )\n",
    "        return morse"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Above, we have packaged the data (`letter_to_morse`) together with the `encode` function into what we call a `class`. A Class describes how data and functions are combined together. An instance of a class is called an object, which we can create by calling `Morse()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "m = Morse()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`m` is an object of the class `Morse`. It has its own copy of `letter_to_morse` within it, and its own copy of the `encode` function. We can call `m`'s copy of the `encode` function by typing `m.encode(...)`, e.g."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "m.encode(\"Hello World\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To create a new class, you use the `class` keyword, followed by the name of your class. In this case, `class Morse` defined a new class called `Morse`. You then add a colon, and write all of the functions that should be part of the class indented below. At a minimum, you must define one function, called the constructor. This function has the signature `def __init__(self, arguments...)`. The first argument, `self`, is a special variable that allows an object of the class to access the data that belongs to itself. It is the job of the constructor to set up that data. For example, let's now create a new class that provides a simple guessing game."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class GuessGame:\n",
    "    def __init__(self, secret):\n",
    "        self._secret = secret\n",
    "        \n",
    "    def guess(self, value):\n",
    "        if (value == self._secret):\n",
    "            print(\"Well done - you have guessed my secret\")\n",
    "        else:\n",
    "            print(\"Try again...\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this class, the constructor `__init__(self, secret)` takes an extra argument after `self`. This argument is saved as the `_secret` variable that is part of the `self` of the object. Note that we always name variables that are part of a class with a leading underscore. We can construct different object instances of GuessGame that have different secrets, e.g."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "g1 = GuessGame(\"cat\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "g2 = GuessGame(\"dog\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here, the `self._secret` for `g1` equals \"cat\". The `self._secret` for `g2` equals \"dog\".\n",
    "\n",
    "When we call the function `g1.guess(value)`, it compares `value` against `self._secret` for `g1`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "g1.guess(\"dog\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "g1.guess(\"cat\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When we call the function `g2.guess(value)` it compares `value` against `self._secret` for `g2`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "g2.guess(\"cat\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "g2.guess(\"dog\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Exercise\n",
    "\n",
    "## Exercise 1\n",
    "\n",
    "Edit the below `GuessGame` example so that it records how many unsuccessful guesses have been performed. Add a function called `nGuesses()` that returns the number of unsuccessful guesses. Once you have made the changes, check your class by creating an object of your class and using it to make some successful and unsuccessful guesses."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class GuessGame:\n",
    "    def __init__(self, secret):\n",
    "        self._secret = secret\n",
    "        \n",
    "    def guess(self, value):\n",
    "        if (value == self._secret):\n",
    "            print(\"Well done - you have guessed my secret\")\n",
    "        else:\n",
    "            print(\"Try again...\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise 2\n",
    "\n",
    "Edit the constructor of your `GuessGame` class so that the user can optionally specify a maximum number of allowable guesses. If the maximum number of guesses is not supplied, then set the default value to 5.\n",
    "\n",
    "Create a `maxGuesses()` function that returns the maximum number of allowable guesses.\n",
    "\n",
    "Finally, edit the `guess()` function so that it will not let you make more than the maximum number of guesses (e.g. if the number of guesses exceeds the maximum number, then print out \"Sorry, you have run out of guesses.\").\n",
    "\n",
    "Check that you code works by creating an object of GuessGame that only allows three guesses, and see what happens if you guess incorrectly more than three times."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "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.5.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
