{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "dressed-approval",
   "metadata": {},
   "source": [
    "<h1>Python Programming: Objects and Classes</h1>\n",
    "\n",
    "\n",
    "<!--\n",
    "\n",
    "## Overview\n",
    "This lesson walks through creating classes in Python. The lesson starts with We Dos for creating a class and instantiating objects from that class. It's followed by a We Do for class and instance variables. It ends with a few partner exercises, then a series of Knowledge Checks.\n",
    "\n",
    "## Learning Objectives\n",
    "In this lesson, students will:\n",
    "\n",
    "- Define a class.\n",
    "- Instantiate an object from a class.\n",
    "- Create classes with class variables.\n",
    "\n",
    "\n",
    "## Duration\n",
    "50 MINUTES\n",
    "\n",
    "## Suggested Agenda\n",
    "\n",
    "| Time | Activity |\n",
    "| --- | --- |\n",
    "| 0:00 - 0:03 | Welcome |\n",
    "| 0:03 - 0:10 | Class Overview |\n",
    "| 0:10 - 0:25 | Creating Classes |\n",
    "| 0:25 - 0:30 | Class Variables |\n",
    "| 0:30 - 0:47 | Exercises |\n",
    "| 0:47 - 0:50 | Summary |\n",
    "\n",
    "## In Class: Materials\n",
    "- Projector\n",
    "- Internet connection\n",
    "- Python 3\n",
    "-->\n",
    "\n",
    "---\n",
    "\n",
    "## Learning Objectives\n",
    "\n",
    "*After this lesson, you will be able to…*\n",
    "\n",
    "- Define a class.\n",
    "- Instantiate an object from a class.\n",
    "- Create classes with default instance variables.\n",
    "\n",
    "---\n",
    "\n",
    "## Blueprints\n",
    "\n",
    "All cars have things that make them a `Car`. Although the details might be different, every type of car has the same basics — it's off the same blueprint, with the same properties and methods.\n",
    "\n",
    "![](https://s3.amazonaws.com/ga-instruction/assets/programming-fundamentals/class-objects-car.png)\n",
    "\n",
    "- Property: A shape (could be hatchback or sedan).\n",
    "- Property: A color (could be red, black, blue, or silver).\n",
    "- Property: Seats (could be between 2 and 5).\n",
    "- Method: Can drive.\n",
    "- Method: Can park.\n",
    "- When we make a car, we can vary the values of these properties. Some cars have economy engines, some have performance engines. Some have four doors, others have two. However, they are all types of `Car`s.\n",
    "\n",
    "---\n",
    "\n",
    "## Introduction: Objects and Classes\n",
    "\n",
    "These properties and behaviors can be thought of as variables and functions.\n",
    "\n",
    "`Car` blueprint:\n",
    "\n",
    "- Properties (variables): `shape`, `color`, `seats`\n",
    "- Methods (functions): `drive()` and `park()`\n",
    "\n",
    "An actual car might have:\n",
    "\n",
    "**Properties - Variables**:\n",
    "- shape = \"hatchback\"\n",
    "- color = \"red\", \"black\", \"blue\", or \"silver\"\n",
    "- seats = 2\n",
    "\n",
    "**Methods - Functions:**\n",
    "- drive()\n",
    "- park()\n",
    "- reverse()\n",
    "\n",
    "\n",
    "\n",
    "**Discussion:** What might a blueprint for a chair look like?\n",
    "\n",
    "---\n",
    "\n",
    "## Discussion: Python Classes\n",
    "\n",
    "In Python, the concept of blueprints and objects is common. A **class** is the blueprint for an **object**. Everything you declare in Python is an object, and it has a class.\n",
    "\n",
    "Consider the `List` class — every list you make has the same basic concept.\n",
    "\n",
    "Variables:\n",
    "\n",
    "- Elements: What's in the list! E.g., `my_list = [element1, element2, element3]`.\n",
    "\n",
    "Methods that all lists have:\n",
    "\n",
    "- `my_list.pop()`, `my_list.append()`, `my.list.insert(index)`\n",
    "\n",
    "![](https://s3.amazonaws.com/ga-instruction/assets/programming-fundamentals/pythonlistclass.png)\n",
    "\n",
    "What behaviors and properties do you think are in the `Dictionary` class? The `Set` class?\n",
    "\n",
    "---\n",
    "\n",
    "## Discussion: A `Dog` Class\n",
    "\n",
    "We can make a class for anything! Let's create a `Dog` class.\n",
    "\n",
    "The objects might be `greyhound`, `goldenRetriever`, `corgi`, etc.\n",
    "\n",
    "Think about the `Dog` blueprint. What variables might our class have? What functions?\n",
    "\n",
    "![](https://s3.amazonaws.com/ga-instruction/assets/programming-fundamentals/class-dog-blueprint.png)\n",
    "\n",
    "**Pro tip:** When functions are in a class, they are called \"methods.\" A method is a function _within the context_ of a class object.\n",
    "\n",
    "**Pro tip:** While objects are named in snake_case, classes are conventionally named in TitleCase.\n",
    "\n",
    "---\n",
    "\n",
    "\n",
    "## We Do: Defining Classes\n",
    "\n",
    "Class definitions are similar to function definitions, but instead of `def`, we use `class`.\n",
    "\n",
    "Let's declare a class for `Dog`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "emotional-secret",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "retired-carter",
   "metadata": {},
   "source": [
    "\n",
    "![](https://s3.amazonaws.com/ga-instruction/assets/programming-fundamentals/class-dog-name-age.png)\n",
    "\n",
    "**Pro tip:** Files are usually named for their class, so the `Dog` class is in `Dog.py`.\n",
    "\n",
    "- It's very common in programming to make our own classes to organize data in the ways we want. Python gives us, for example, a string and an integer.\n",
    "- What if we want to store a bunch of data where each item contains a string **and** an integer **and** a function that prints them out nicely?\n",
    "- We can make a class for that, and then each object we make from that class will have that all baked in.\n",
    "- Note that we can **optionally** (as of Python3) include parentheses after our class declaration, i.e. `class Dog():`\n",
    "- All classes, if no explicit inheritance is given, will inherit from the `Object` class (the parent of all Python classes)\n",
    "- The base class can be shown by typing `<your_class-name>.__bases__`. If you haven't specified a parent, it should return `object`\n",
    "- `class Dog:` is functionally identical to `class Dog():` and `class Dog(object):`.\n",
    "\n",
    "---\n",
    "\n",
    "## We Do: Adding Docstrings, Part 1\n",
    "\n",
    "Using our `Dog` class from the previous exercise, let's add a _docstring_.\n",
    "\n",
    "A [docstring](https://www.python.org/dev/peps/pep-0257/#what-is-a-docstring) is a comment that is placed at the top of a class (or function). When python precompiles the class or function (which happens in the background), it parses this comment and makes it available to the user as a helpfile when the class is accessed when typing the class or function, followed by a `?` question mark. In a Jupyter Notebook, this can also be accessed by placing your cursor at the end of a class or function and pressing `SHIFT + TAB`. Try it out! Enter `list?` in a python interpreter or Jupyter cell and run it. What is returned?\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "instrumental-ancient",
   "metadata": {},
   "outputs": [],
   "source": [
    "list?\n",
    "# help(list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "varying-contributor",
   "metadata": {},
   "source": [
    "\n",
    "Here, we can see under the `Docstring` section are the 'instructions for use', and the `Init signature` is a list of arguments that can be passed to the function, which is automatically recognized and generated by the python compiler.\n",
    "\n",
    "Check out this link for some best practices for writing doc strings: [PEP-0257](https://www.python.org/dev/peps/pep-0257/)\n",
    "\n",
    "---\n",
    "\n",
    "## We Do: Adding Docstrings, Part 2\n",
    "\n",
    "Using our `Dog` class from the previous exercise, let's add a _docstring_.\n",
    "\n",
    "The docstring for a function or method should summarize its behavior and document its arguments, return value(s), side effects, exceptions raised, and restrictions on when it can be called. Not all of these will be applicable for every function or method, so the programmer should take care to document the parts relevant. Optional arguments should be indicated. It should be documented whether keyword arguments are part of the interface.\n",
    "\n",
    "The first line is a simple description of the class or function, followed by a blank line, followed by keyword arguments, if any.\n",
    "\n",
    "Let's write a docstring for `Dog`:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "separated-waterproof",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Dog:\n",
    "    \"\"\"Creates Dog class, possible child class of Animal\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    name : str, default blank\n",
    "        Desired name of Dog\n",
    "    age : int, default 0\n",
    "        Age of Dog in years\n",
    "    \"\"\"\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "occupational-plane",
   "metadata": {},
   "source": [
    "\n",
    "- Also what is usually included in a class docstring is a 'see also' section, for similar classes, as well as an 'example' section that shows how the class is instantiated and used with some common use cases. This was omitted for brevity.\n",
    "- An important part of this docstring is the `type` of the parameter for each parameter. This tells the user how to input data into the class. For example, we _could have_ inserted our age as a string, but we are expecting an int. Since python is not strongly typed, it is important to tell the user of your code what types of variables should be passed.\n",
    "- Also important for methods is the `return type` of the method (if any). This will let the user know if they need to 'catch' any object that is returned from the method into a variable.\n",
    "\n",
    "---\n",
    "\n",
    "## We Do: The `__init__` Method\n",
    "\n",
    "What first? Every class starts with an `__init__` method. It's:\n",
    "\n",
    "* Where we define the class' variables.\n",
    "* Short for \"initialize.\"\n",
    "  * \"Every time you make an object from this class, do what's in here.\"\n",
    "\n",
    "Let's add this:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "refined-telephone",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Dog:\n",
    "    \"\"\"Creates Dog class, possible child class of Animal\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    name : str, default blank\n",
    "        Desired name of Dog\n",
    "    age : int, default 0\n",
    "        Age of Dog in years\n",
    "    \"\"\"\n",
    "\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "formed-daniel",
   "metadata": {},
   "source": [
    "\n",
    "*Note: `self` means \"each individual object made from this class.\" Not every \"dog\" has the same name!*\n",
    "*Note: in the effort of brevity, we wil be omitting the docstring from future examples!*\n",
    "\n",
    "- When we make an object, we'll first set its variables.\n",
    "- The first argument passed to the `__init__` function, `self`, is required when defining methods for classes. The `self` argument is a reference to a future instantiation of the class. In other words, `self` refers to each individual dog.\n",
    "- This lets each object made from a class keep references to its own data and function members. Not every \"dog\" has the same attributes, so we want individual dogs to maintain their own attributes.\n",
    "- Python allows us to provide default values for parameters in any function we provide. Here, if no `name` or `age` values are provided when a `Dog` is initialized, they will default. To create default values, we assign values to the parameters *inside* the parentheses. You've seen this!\n",
    "\n",
    "---\n",
    "\n",
    "## We Do: Adding a `bark_hello()` Method\n",
    "\n",
    "All dogs have the behavior `bark`, so let's add that. This is a regular function (method), just inside the class!\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "advanced-processing",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "received-alignment",
   "metadata": {},
   "source": [
    "\n",
    "We're done defining the class!\n",
    "\n",
    "*Note: We have also added a docstring to our new `.bark_hello()` method. This will be omitted for future examples for brevity.*\n",
    "\n",
    "---\n",
    "\n",
    "## Aside: Instantiating Objects From Classes\n",
    "\n",
    "Now we have a `Dog` template!\n",
    "\n",
    "Each `dog` object we make from this template:\n",
    "\n",
    "* Has a name.\n",
    "* Has an age.\n",
    "* Can bark.\n",
    "\n",
    "---\n",
    "\n",
    "## We Do: How Do We Make a `Dog` Object?\n",
    "\n",
    "We call our class name like we call a function — passing in arguments, which go to the `init`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "animated-produce",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "suspended-bahrain",
   "metadata": {},
   "source": [
    "- This will create a new object according to our `Dog` class specification.\n",
    "- Python runs our `__init__` method to initialize the object.\n",
    "- Here, we are telling our `__init__` method to set the name of this `dog` to `'Gracie'` and set her age to `8` years old.\n",
    "- Even though `self` is listed as a parameter for the `bark_hello()` function, we don't pass it into the function. It happens automatically.\n",
    "\n",
    "---\n",
    "\n",
    "## We Do: Adding Print\n",
    "\n",
    "`__init__` is just a method. It creates variables, but we can also add a `print` statement! This will run when we create the object.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "generous-litigation",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "straight-elder",
   "metadata": {},
   "source": [
    "- Reminder: \"Method\" is a function in a class.\n",
    "- Note that `print` statements can be anywhere — it's not a variable! `__init__` is a method.\n",
    "- The `__init__` method will execute once and only once when you create a new object from a class. Note that the `print` statement never happens again.\n",
    "\n",
    "---\n",
    "\n",
    "## Quick Review: Classes\n",
    "\n",
    "A class is a blueprint for an object. Some classes are built into Python, like `List`. We can always make a `list` object.\n",
    "\n",
    "We can make a class for anything!\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "missing-soccer",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Created like a function; TitleCase\n",
    "class Dog:\n",
    "\n",
    "    # __init__: A method (function) that happens just once, when the object is created.\n",
    "    def __init__(self, name=\"\", age=0): # What's passed in to the class is used here.\n",
    "        # Set variables for each.\n",
    "        self.name = name\n",
    "        self.age = age\n",
    "        print(name, \"created.\") # This will run when the __init__ method is called.\n",
    "\n",
    "  # Classes can have as many methods (functions) as you'd like.\n",
    "    def bark_hello(self):\n",
    "        print(\"Woof! I am called\", self.name, \"; I am\", self.age, \"human-years old.\")\n",
    "\n",
    "fox = Dog(\"Fox\") # Creating the object calls __init__. Objects are snake_case.\n",
    "print(\"This dog's name is\", fox.name) # The object now has those variables!\n",
    "fox.bark_hello() # The object now has those methods and variables!\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "printable-nursery",
   "metadata": {},
   "source": [
    "\n",
    "---\n",
    "\n",
    "## Discussion: What About Tea?\n",
    "\n",
    "Let's make a `TeaCup` class.\n",
    "\n",
    "* What variables would a cup of tea have?\n",
    "* What methods?\n",
    "\n",
    "---\n",
    "\n",
    "## A Potential `TeaCup` Class\n",
    "\n",
    "We could say:\n",
    "\n",
    "Variables:\n",
    "\n",
    "* A total `capacity`.\n",
    "* A current `amount`.\n",
    "\n",
    "Methods:\n",
    "\n",
    "* `fill()` our cup.\n",
    "* `empty()` our cup.\n",
    "* `drink()` some tea from our cup.\n",
    "\n",
    "Remember:\n",
    "- Given a well-defined cup of tea, we can use the class definition to create **instances** of the class.\n",
    "- Each **instance** of the `TeaCup` class can have a different `capacity` and keep track of different `amounts`.\n",
    "- Although different, properties are affected by methods like `fill()`, `empty()`, and `drink()` similarly.\n",
    "\n",
    "---\n",
    "\n",
    "## Example: A `TeaCup` Class\n",
    "\n",
    "\n",
    "Here's what a `TeaCup` class definition might look like in Python:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "final-parks",
   "metadata": {},
   "outputs": [],
   "source": [
    "class TeaCup:\n",
    "    def __init__(self, capacity):\n",
    "        # Python executes when a new cup of tea is created.\n",
    "        self.capacity = capacity # Total ounces the cup holds.\n",
    "        self.amount = 0 # Current ounces in the cup. All cups start empty!\n",
    "\n",
    "    def fill(self):\n",
    "        self.amount = self.capacity\n",
    "\n",
    "    def empty(self):\n",
    "        self.amount = 0\n",
    "\n",
    "    def drink(self, amount_drank):\n",
    "        self.amount -= amount_drank\n",
    "        # Prevent negative amounts\n",
    "        if (self.amount < 0):\n",
    "            self.amount = 0\n",
    "\n",
    "steves_cup = TeaCup(12)  # Maybe a fancy tea latte.\n",
    "yis_cup = TeaCup(16)    # It's a rough morning!\n",
    "brandis_cup = TeaCup(2)  # Just a quick sip."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "incident-services",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "activated-rover",
   "metadata": {},
   "source": [
    "\n",
    "---\n",
    "\n",
    "## Quick Knowledge Check:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "international-ultimate",
   "metadata": {},
   "outputs": [],
   "source": [
    "class TeaCup:\n",
    "    def __init__(self, capacity = 8):\n",
    "        self.capacity = capacity\n",
    "        self.amount = 0\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "manual-lounge",
   "metadata": {},
   "source": [
    "When will the capacity be `8`?\n",
    "\n",
    "---\n",
    "\n",
    "## Variables for All Class Objects\n",
    "\n",
    "Next up: new types of class variables!\n",
    "\n",
    "Let's revisit our `Dog` class:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "driven-herald",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Dog:\n",
    "\n",
    "    def __init__(self, name=\"\", age=0):\n",
    "        self.name = name\n",
    "        self.age = age\n",
    "        print(name, \"created.\")\n",
    "\n",
    "    def bark_hello(self):\n",
    "        print(\"Woof! I am called\", self.name, \"; I am\", self.age, \"human-years old\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "designed-yugoslavia",
   "metadata": {},
   "source": [
    "\n",
    "What if there are variables that we want across all dogs?\n",
    "\n",
    "For example, can we count how many `dog` objects we make and track it in the class?\n",
    "\n",
    "- Our `Dog` class had variables attached to `self` that exist independently for each object that's created.\n",
    "    * Each object instance has its own copies of these variables, and they can vary across objects.\n",
    "- We can attach variables to the class itself so that there's one single thing that exists for an entire class.\n",
    "\n",
    "---\n",
    "\n",
    "## I Do: Class vs. Instance Members\n",
    "\n",
    "We already have **instance variables**, which are specific to each `dog` object (each has its own name!).\n",
    "\n",
    "A **class variable** is specific to the class, regardless of the object. It's created **above** `__init__`.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "certain-shuttle",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Dog:\n",
    "\n",
    "    ### Here, we define class variables. ###\n",
    "    # These are the same for ALL dogs.\n",
    "    \n",
    "\n",
    "    def __init__(self, name=\"\", age=0):\n",
    "\n",
    "        ### These are instance variables. ###\n",
    "        self.name = name\n",
    "        self.age = age\n",
    "        print(name, \"created.\")\n",
    "\n",
    "    def bark_hello(self):\n",
    "    print(\"Woof! I am called\", self.name, \"; I am\", self.age, \"human-years old\")\n",
    "    print(\"There are\", Dog.total_dogs, \"dogs in this room!\") # There's no \"self\" — we call the Dog class name!\n",
    "\n",
    "molly = Dog(\"Molly\", 8)\n",
    "molly.bark_hello()\n",
    "\n",
    "sheera = Dog(\"Sheera\", 5)\n",
    "sheera.bark_hello()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "amateur-motorcycle",
   "metadata": {},
   "source": [
    "- Our `Dog` class had variables attached to `self` that exist independently for each object that's created.\n",
    "    * These are called **instance variables**.\n",
    "    * Each object instance has its own copies of these variables, and they can vary across objects.\n",
    "- We can attach variables to the class itself so that there's one single thing that exists for an entire class.\n",
    "    * These are called **class variables**.\n",
    "\n",
    "---\n",
    "\n",
    "## I Do: Tallying Dogs\n",
    "\n",
    "We can increment the `class` variable any time.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "searching-behalf",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Dog:\n",
    "\n",
    "    ### Here, we define class variables. ###\n",
    "    # These are the same for ALL dogs.\n",
    "    total_dogs = 0\n",
    "\n",
    "    def __init__(self, name=\"\", age=0):\n",
    "\n",
    "        ### These are instance variables. ###\n",
    "        self.name = name\n",
    "        self.age = age\n",
    "        print(name, \"created.\")\n",
    "        \n",
    "\n",
    "    def bark_hello(self):\n",
    "    print(\"Woof! I am called\", self.name, \"; I am\", self.age, \"human-years old\")\n",
    "    print(\"There are\", Dog.total_dogs, \"dogs in this room!\") # There's no \"self\" — we call the Dog class name!\n",
    "\n",
    "molly = Dog(\"Molly\", 8)\n",
    "molly.bark_hello()\n",
    "\n",
    "sheera = Dog(\"Sheera\", 5)\n",
    "sheera.bark_hello()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eleven-spiritual",
   "metadata": {},
   "source": [
    "\n",
    "---\n",
    "\n",
    "## Partner Exercise: Create a Music Genre Class\n",
    "\n",
    "Pair up!\n",
    "- Define a class, `Band`, with these instance variables: `\"genre\"`, `\"band_name\"`, and `\"albums_released\"` (defaulting to `0`).\n",
    "- Give `Band` a method called `print_stats()`, which prints a string like `\"The rock band Queen has 15 albums.\"`\n",
    "- Create a class variable, `number_of_bands`, that tracks the number of bands created.\n",
    "\n",
    "Test your code with calls like: `my_band = (\"rock\", \"Queen\", 15)`. Check out the `print_stats` method and the `number_of_bands` class variable.\n",
    "\n",
    "\n",
    "**Bonus:** If the genre provided isn't `\"pop\"`, `\"classical\"`, or `\"rock\"`, print out `\"This isn't a genre I know.\"`\n",
    "\n",
    "- Things to think about:\n",
    "    * Starting values for variables should be set in the `__init__` method.\n",
    "    * Class variables are declared inside the class but outside any methods.\n",
    "    * Instance variables are declared inside the `__init__` method.\n",
    "    * Does your `__init__` method need to accept any parameters?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "quantitative-adjustment",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "overhead-forward",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## Partner Exercise: Create a `BankAccount` Class\n",
    "\n",
    "Switch drivers! Create a new class `Bank`.\n",
    "\n",
    "Bank accounts should:\n",
    "\n",
    "* Be created with the `account_type` property (either `\"savings\"` or `\"checking\"`).\n",
    "* Keep track of its current `balance`, which always starts at `0`.\n",
    "* Have access to `deposit()` and `withdraw()` methods, which take in an integer and update `balance` accordingly.\n",
    "* Have a class-level variable tracking the total amount of money in all accounts, adding or subtracting whenever `balance` changes.\n",
    "\n",
    "**Bonus:** Start each account with an additional `overdraft_fees` property that begins at `0`. If a call to `withdraw()` ends with the `balance` below `0`, then `overdraft_fees` should be incremented by `20`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "adopted-needle",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "nuclear-order",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## Knowledge Check: Select the Best Answer\n",
    "\n",
    "Consider the following class definition for `Cat`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "alien-smell",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Cat:\n",
    "    def __init__(self, name='Lucky'):\n",
    "        self.name = name\n",
    "        self.fur = short\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "authorized-incident",
   "metadata": {},
   "source": [
    "\n",
    "How would you instantiate a `Cat` object with the `name` attribute `'Furball'`?\n",
    "\n",
    "1. `mycat = Cat(name='Furball')`\n",
    "2. `furball = Cat`\n",
    "3. `mycat = Cat(self, name='Furball')`\n",
    "4. `mycat = Cat.init(name='Furball')`\n",
    "\n",
    "---\n",
    "\n",
    "## Knowledge Check: Select All That Apply.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "improved-secretariat",
   "metadata": {},
   "source": [
    "- The user does not need to supply `self` when using instance methods.\n",
    "- The `self` argument is a reference to the instance object.\n",
    "- Any variable assigned with `self` (e.g., `self.var`) will be shared across instances of the class.\n",
    "- With an instance object, `obj`, entering `obj.self.var` will provide the value for `var` for that instance.\n",
    "\n",
    "---\n",
    "\n",
    "## Knowledge Check: Select the Best Answer\n",
    "\n",
    "Consider the following code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "quarterly-harassment",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Shape(object):\n",
    "    possible = ['triangle','square','circle','pentagon','polygon','rectangle']\n",
    "\n",
    "    def __init__(self, label='triangle'):\n",
    "        self.label = label\n",
    "\n",
    "    def is_possible(self):\n",
    "        if self.label in self.possible:\n",
    "            print('This is possible')\n",
    "        else:\n",
    "            print('This is impossible')\n",
    "\n",
    "square = Shape(label='square')\n",
    "wormhole = Shape(label='wormhole')\n",
    "square.possible.append('wormhole')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "immediate-skill",
   "metadata": {},
   "source": [
    "If you were to enter `wormhole.is_possible()`, would the outcome be `\"This is possible\"` or `\"This is impossible\"`?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "constant-basic",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "explicit-lighting",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## Summary: Discussion\n",
    "\n",
    "Let's chat! Can anyone explain:\n",
    "\n",
    "- What a class is?\n",
    "- What `__init__` does?\n",
    "- What an object is?\n",
    "- The point of `self`?\n",
    "- The two types of variables?\n",
    "\n",
    "---\n",
    "\n",
    "## Summary and Q&A\n",
    "\n",
    "Class:\n",
    "\n",
    "* A pre-defined structure that contains attributes and behaviors grouped together.\n",
    "* The blueprint.\n",
    "* Defined via a method call.\n",
    "* Contains an `__init__` method that takes in parameters to be assigned to an object.\n",
    "* E.g., the `Dog` class; the `List` class.\n",
    "\n",
    "Object:\n",
    "\n",
    "* An instance of a class structure.  \n",
    "* The items built from the blueprint.\n",
    "* E.g., the `gracie` object; the `my_list` object.\n",
    "\n",
    "---\n",
    "\n",
    "## Summary: Types of Variables in a Class\n",
    "\n",
    "Instance variables:\n",
    "\n",
    "- Contain data types declared in the class but defined in each object.\n",
    "- Each `dog` has its own name and age.\n",
    "- Each `my_list` has its own elements.\n",
    "\n",
    "Class variables:\n",
    "\n",
    "- Contain data and methods that span across all objects.\n",
    "- How many `dog` objects are there in total?\n",
    "- The `self` keyword lets us distinguish between variables that exist at the class level versus in each object."
   ]
  }
 ],
 "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.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
