diff --git a/12-Final Capstone Python Project/.ipynb_checkpoints/03-Final Capstone Suggested Walkthrough-checkpoint.ipynb b/12-Final Capstone Python Project/.ipynb_checkpoints/03-Final Capstone Suggested Walkthrough-checkpoint.ipynb
new file mode 100644
index 000000000..f41327d68
--- /dev/null
+++ b/12-Final Capstone Python Project/.ipynb_checkpoints/03-Final Capstone Suggested Walkthrough-checkpoint.ipynb
@@ -0,0 +1,748 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Final Capstone Project - Suggested Walkthrough:\n",
+ "\n",
+ "This is a suggested method for handling one of the Final Capstone Projects. We start by coding out the strictest requirements, and then build out from a working baseline model. Feel free to adapt this solution, and add features you think could help. Good luck!\n",
+ "\n",
+ "\n",
+ "## Bank Account Manager\n",
+ "\n",
+ "Under the Classes section in the list of suggested final capstone projects is a Bank Account Manager program. The goal is to create a class called Account which will be an abstract class for three other classes called CheckingAccount, SavingsAccount and BusinessAccount. Then you should manage credits and debits from these accounts through an ATM style program."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Project Scope\n",
+ "To tackle this project, first consider what has to happen.\n",
+ "1. There will be three different types of bank account (Checking, Savings, Business)\n",
+ "2. Each account will accept deposits and withdrawals, and will need to report balances"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Project Wishlist\n",
+ "We might consider additional features, like:\n",
+ "* impose a monthly maintenance fee\n",
+ "* waive fees for minimum combined deposit balances\n",
+ "* each account may have additional properties unique to that account:\n",
+ " * Checking allows unlimited transactions, and may keep track of printed checks\n",
+ " * Savings limits the number of withdrawals per period, and may earn interest\n",
+ " * Business may impose transaction fees\n",
+ "* automatically transfer the \"change\" for debit card purchases from Checking to Savings,
where \"change\" is the amount needed to raise a debit to the nearest whole dollar\n",
+ "* permit savings autodraft overdraft protection\n",
+ "\n",
+ "### Let's get started!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Step 1: Establish an abstract Account class with features shared by all accounts.\n",
+ "Note that abstract classes are never instantiated, they simply provide a base class with attributes and methods to be inherited by any derived class."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Account:\n",
+ " # Define an __init__ constructor method with attributes shared by all accounts:\n",
+ " def __init__(self,acct_nbr,opening_deposit):\n",
+ " self.acct_nbr = acct_nbr\n",
+ " self.balance = opening_deposit\n",
+ " \n",
+ " # Define a __str__ mehthod to return a recognizable string to any print() command\n",
+ " def __str__(self):\n",
+ " return f'${self.balance:.2f}'\n",
+ " \n",
+ " # Define a universal method to accept deposits\n",
+ " def deposit(self,dep_amt):\n",
+ " self.balance += dep_amt\n",
+ "\n",
+ " # Define a universal method to handle withdrawals\n",
+ " def withdraw(self,wd_amt):\n",
+ " if self.balance >= wd_amt:\n",
+ " self.balance -= wd_amt\n",
+ " else:\n",
+ " return 'Funds Unavailable'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Step 2: Establish a Checking Account class that inherits from Account, and adds Checking-specific traits."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Checking(Account):\n",
+ " def __init__(self,acct_nbr,opening_deposit):\n",
+ " # Run the base class __init__\n",
+ " super().__init__(acct_nbr,opening_deposit)\n",
+ "\n",
+ " # Define a __str__ method that returns a string specific to Checking accounts\n",
+ " def __str__(self):\n",
+ " return f'Checking Account #{self.acct_nbr}\\n Balance: {Account.__str__(self)}'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Step 3: TEST setting up a Checking Account object"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x = Checking(54321,654.33)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Checking Account #54321\n",
+ " Balance: $654.33\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(x)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'Funds Unavailable'"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "x.withdraw(1000)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x.withdraw(30)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "624.33"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "x.balance"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Step 4: Set up similar Savings and Business account classes"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Savings(Account):\n",
+ " def __init__(self,acct_nbr,opening_deposit):\n",
+ " # Run the base class __init__\n",
+ " super().__init__(acct_nbr,opening_deposit)\n",
+ "\n",
+ " # Define a __str__ method that returns a string specific to Savings accounts\n",
+ " def __str__(self):\n",
+ " return f'Savings Account #{self.acct_nbr}\\n Balance: {Account.__str__(self)}'\n",
+ "\n",
+ "\n",
+ "class Business(Account):\n",
+ " def __init__(self,acct_nbr,opening_deposit):\n",
+ " # Run the base class __init__\n",
+ " super().__init__(acct_nbr,opening_deposit)\n",
+ "\n",
+ " # Define a __str__ method that returns a string specific to Business accounts\n",
+ " def __str__(self):\n",
+ " return f'Business Account #{self.acct_nbr}\\n Balance: {Account.__str__(self)}'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**At this point** we've met the minimum requirement for the assignment. We have three different bank account classes. Each one can accept deposits, make withdrawals and report a balance, as they each inherit from an abstract Account base class.\n",
+ "\n",
+ "So now the fun part - let's add some features!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Step 5: Create a Customer class\n",
+ "\n",
+ "For this next phase, let's set up a Customer class that holds a customer's name and PIN and can contain any number and/or combination of Account objects."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Customer:\n",
+ " def __init__(self, name, PIN):\n",
+ " self.name = name\n",
+ " self.PIN = PIN\n",
+ " \n",
+ " # Create a dictionary of accounts, with lists to hold multiple accounts\n",
+ " self.accts = {'C':[],'S':[],'B':[]}\n",
+ " \n",
+ " def __str__(self):\n",
+ " return self.name\n",
+ " \n",
+ " def open_checking(self,acct_nbr,opening_deposit):\n",
+ " self.accts['C'].append(Checking(acct_nbr,opening_deposit))\n",
+ " \n",
+ " def open_savings(self,acct_nbr,opening_deposit):\n",
+ " self.accts['S'].append(Savings(acct_nbr,opening_deposit))\n",
+ " \n",
+ " def open_business(self,acct_nbr,opening_deposit):\n",
+ " self.accts['B'].append(Business(acct_nbr,opening_deposit))\n",
+ " \n",
+ " # rather than maintain a running total of deposit balances,\n",
+ " # write a method that computes a total as needed\n",
+ " def get_total_deposits(self):\n",
+ " total = 0\n",
+ " for acct in self.accts['C']:\n",
+ " print(acct)\n",
+ " total += acct.balance\n",
+ " for acct in self.accts['S']:\n",
+ " print(acct)\n",
+ " total += acct.balance\n",
+ " for acct in self.accts['B']:\n",
+ " print(acct)\n",
+ " total += acct.balance\n",
+ " print(f'Combined Deposits: ${total}')\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Step 6: TEST setting up a Customer, adding accounts, and checking balances"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "bob = Customer('Bob',1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "bob.open_checking(321,555.55)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Checking Account #321\n",
+ " Balance: $555.55\n",
+ "Combined Deposits: $555.55\n"
+ ]
+ }
+ ],
+ "source": [
+ "bob.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "bob.open_savings(564,444.66)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Checking Account #321\n",
+ " Balance: $555.55\n",
+ "Savings Account #564\n",
+ " Balance: $444.66\n",
+ "Combined Deposits: $1000.21\n"
+ ]
+ }
+ ],
+ "source": [
+ "bob.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "nancy = Customer('Nancy',2)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "nancy.open_business(2018,8900)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Business Account #2018\n",
+ " Balance: $8900.00\n",
+ "Combined Deposits: $8900\n"
+ ]
+ }
+ ],
+ "source": [
+ "nancy.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Wait!** Why don't Nancy's combined deposits show a decimal?
This is easily fixed in the class definition (mostly copied from above, with a change made to the last line of code):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Customer:\n",
+ " def __init__(self, name, PIN):\n",
+ " self.name = name\n",
+ " self.PIN = PIN\n",
+ " self.accts = {'C':[],'S':[],'B':[]}\n",
+ "\n",
+ " def __str__(self):\n",
+ " return self.name\n",
+ " \n",
+ " def open_checking(self,acct_nbr,opening_deposit):\n",
+ " self.accts['C'].append(Checking(acct_nbr,opening_deposit))\n",
+ " \n",
+ " def open_savings(self,acct_nbr,opening_deposit):\n",
+ " self.accts['S'].append(Savings(acct_nbr,opening_deposit))\n",
+ " \n",
+ " def open_business(self,acct_nbr,opening_deposit):\n",
+ " self.accts['B'].append(Business(acct_nbr,opening_deposit))\n",
+ " \n",
+ " def get_total_deposits(self):\n",
+ " total = 0\n",
+ " for acct in self.accts['C']:\n",
+ " print(acct)\n",
+ " total += acct.balance\n",
+ " for acct in self.accts['S']:\n",
+ " print(acct)\n",
+ " total += acct.balance\n",
+ " for acct in self.accts['B']:\n",
+ " print(acct)\n",
+ " total += acct.balance\n",
+ " print(f'Combined Deposits: ${total:.2f}') # added precision formatting here"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**So it's fixed, right?**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Business Account #2018\n",
+ " Balance: $8900.00\n",
+ "Combined Deposits: $8900\n"
+ ]
+ }
+ ],
+ "source": [
+ "nancy.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Nope!** Changes made to the class definition do *not* affect objects created under different sets of instructions.
To fix Nancy's account, we have to build her record from scratch."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Business Account #2018\n",
+ " Balance: $8900.00\n",
+ "Combined Deposits: $8900.00\n"
+ ]
+ }
+ ],
+ "source": [
+ "nancy = Customer('Nancy',2)\n",
+ "nancy.open_business(2018,8900)\n",
+ "nancy.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### This is why testing is so important!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Step 7: Let's write some functions for making deposits and withdrawals.\n",
+ "\n",
+ "Be sure to include a docstring that explains what's expected by the function!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def make_dep(cust,acct_type,acct_num,dep_amt):\n",
+ " \"\"\"\n",
+ " make_dep(cust, acct_type, acct_num, dep_amt)\n",
+ " cust = variable name (Customer record/ID)\n",
+ " acct_type = string 'C' 'S' or 'B'\n",
+ " acct_num = integer\n",
+ " dep_amt = integer\n",
+ " \"\"\"\n",
+ " for acct in cust.accts[acct_type]:\n",
+ " if acct.acct_nbr == acct_num:\n",
+ " acct.deposit(dep_amt)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "make_dep(nancy,'B',2018,67.45)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Business Account #2018\n",
+ " Balance: $8967.45\n",
+ "Combined Deposits: $8967.45\n"
+ ]
+ }
+ ],
+ "source": [
+ "nancy.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def make_wd(cust,acct_type,acct_num,wd_amt):\n",
+ " \"\"\"\n",
+ " make_dep(cust, acct_type, acct_num, wd_amt)\n",
+ " cust = variable name (Customer record/ID)\n",
+ " acct_type = string 'C' 'S' or 'B'\n",
+ " acct_num = integer\n",
+ " wd_amt = integer\n",
+ " \"\"\"\n",
+ " for acct in cust.accts[acct_type]:\n",
+ " if acct.acct_nbr == acct_num:\n",
+ " acct.withdraw(wd_amt)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "make_wd(nancy,'B',2018,1000000)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Business Account #2018\n",
+ " Balance: $8967.45\n",
+ "Combined Deposits: $8967.45\n"
+ ]
+ }
+ ],
+ "source": [
+ "nancy.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**What happened??** We seemed to successfully make a withdrawal, but nothing changed!
This is because, at the very beginning, we had our Account class *return* the string 'Funds Unavailable' instead of print it. If we change that here, we'll have to also run the derived class definitions, and Nancy's creation, but *not* the Customer class definition. Watch:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Account:\n",
+ " def __init__(self,acct_nbr,opening_deposit):\n",
+ " self.acct_nbr = acct_nbr\n",
+ " self.balance = opening_deposit\n",
+ " \n",
+ " def __str__(self):\n",
+ " return f'${self.balance:.2f}'\n",
+ " \n",
+ " def deposit(self,dep_amt):\n",
+ " self.balance += dep_amt\n",
+ " \n",
+ " def withdraw(self,wd_amt):\n",
+ " if self.balance >= wd_amt:\n",
+ " self.balance -= wd_amt\n",
+ " else:\n",
+ " print('Funds Unavailable') # changed \"return\" to \"print\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Checking(Account):\n",
+ " def __init__(self,acct_nbr,opening_deposit):\n",
+ " super().__init__(acct_nbr,opening_deposit)\n",
+ " \n",
+ " def __str__(self):\n",
+ " return f'Checking Account #{self.acct_nbr}\\n Balance: {Account.__str__(self)}'\n",
+ "\n",
+ " \n",
+ "class Savings(Account):\n",
+ " def __init__(self,acct_nbr,opening_deposit):\n",
+ " super().__init__(acct_nbr,opening_deposit)\n",
+ "\n",
+ " def __str__(self):\n",
+ " return f'Savings Account #{self.acct_nbr}\\n Balance: {Account.__str__(self)}'\n",
+ "\n",
+ "\n",
+ "class Business(Account):\n",
+ " def __init__(self,acct_nbr,opening_deposit):\n",
+ " super().__init__(acct_nbr,opening_deposit)\n",
+ "\n",
+ " def __str__(self):\n",
+ " return f'Business Account #{self.acct_nbr}\\n Balance: {Account.__str__(self)}'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Business Account #2018\n",
+ " Balance: $8900.00\n",
+ "Combined Deposits: $8900.00\n"
+ ]
+ }
+ ],
+ "source": [
+ "nancy = Customer('Nancy',2)\n",
+ "nancy.open_business(2018,8900)\n",
+ "nancy.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Funds Unavailable\n"
+ ]
+ }
+ ],
+ "source": [
+ "make_wd(nancy,'B',2018,1000000)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Business Account #2018\n",
+ " Balance: $8900.00\n",
+ "Combined Deposits: $8900.00\n"
+ ]
+ }
+ ],
+ "source": [
+ "nancy.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Good job!"
+ ]
+ }
+ ],
+ "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.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/12-Final Capstone Python Project/03-Final Capstone Suggested Walkthrough.ipynb b/12-Final Capstone Python Project/03-Final Capstone Suggested Walkthrough.ipynb
new file mode 100644
index 000000000..f41327d68
--- /dev/null
+++ b/12-Final Capstone Python Project/03-Final Capstone Suggested Walkthrough.ipynb
@@ -0,0 +1,748 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Final Capstone Project - Suggested Walkthrough:\n",
+ "\n",
+ "This is a suggested method for handling one of the Final Capstone Projects. We start by coding out the strictest requirements, and then build out from a working baseline model. Feel free to adapt this solution, and add features you think could help. Good luck!\n",
+ "\n",
+ "\n",
+ "## Bank Account Manager\n",
+ "\n",
+ "Under the Classes section in the list of suggested final capstone projects is a Bank Account Manager program. The goal is to create a class called Account which will be an abstract class for three other classes called CheckingAccount, SavingsAccount and BusinessAccount. Then you should manage credits and debits from these accounts through an ATM style program."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Project Scope\n",
+ "To tackle this project, first consider what has to happen.\n",
+ "1. There will be three different types of bank account (Checking, Savings, Business)\n",
+ "2. Each account will accept deposits and withdrawals, and will need to report balances"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Project Wishlist\n",
+ "We might consider additional features, like:\n",
+ "* impose a monthly maintenance fee\n",
+ "* waive fees for minimum combined deposit balances\n",
+ "* each account may have additional properties unique to that account:\n",
+ " * Checking allows unlimited transactions, and may keep track of printed checks\n",
+ " * Savings limits the number of withdrawals per period, and may earn interest\n",
+ " * Business may impose transaction fees\n",
+ "* automatically transfer the \"change\" for debit card purchases from Checking to Savings,
where \"change\" is the amount needed to raise a debit to the nearest whole dollar\n",
+ "* permit savings autodraft overdraft protection\n",
+ "\n",
+ "### Let's get started!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Step 1: Establish an abstract Account class with features shared by all accounts.\n",
+ "Note that abstract classes are never instantiated, they simply provide a base class with attributes and methods to be inherited by any derived class."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Account:\n",
+ " # Define an __init__ constructor method with attributes shared by all accounts:\n",
+ " def __init__(self,acct_nbr,opening_deposit):\n",
+ " self.acct_nbr = acct_nbr\n",
+ " self.balance = opening_deposit\n",
+ " \n",
+ " # Define a __str__ mehthod to return a recognizable string to any print() command\n",
+ " def __str__(self):\n",
+ " return f'${self.balance:.2f}'\n",
+ " \n",
+ " # Define a universal method to accept deposits\n",
+ " def deposit(self,dep_amt):\n",
+ " self.balance += dep_amt\n",
+ "\n",
+ " # Define a universal method to handle withdrawals\n",
+ " def withdraw(self,wd_amt):\n",
+ " if self.balance >= wd_amt:\n",
+ " self.balance -= wd_amt\n",
+ " else:\n",
+ " return 'Funds Unavailable'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Step 2: Establish a Checking Account class that inherits from Account, and adds Checking-specific traits."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Checking(Account):\n",
+ " def __init__(self,acct_nbr,opening_deposit):\n",
+ " # Run the base class __init__\n",
+ " super().__init__(acct_nbr,opening_deposit)\n",
+ "\n",
+ " # Define a __str__ method that returns a string specific to Checking accounts\n",
+ " def __str__(self):\n",
+ " return f'Checking Account #{self.acct_nbr}\\n Balance: {Account.__str__(self)}'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Step 3: TEST setting up a Checking Account object"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x = Checking(54321,654.33)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Checking Account #54321\n",
+ " Balance: $654.33\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(x)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'Funds Unavailable'"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "x.withdraw(1000)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x.withdraw(30)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "624.33"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "x.balance"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Step 4: Set up similar Savings and Business account classes"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Savings(Account):\n",
+ " def __init__(self,acct_nbr,opening_deposit):\n",
+ " # Run the base class __init__\n",
+ " super().__init__(acct_nbr,opening_deposit)\n",
+ "\n",
+ " # Define a __str__ method that returns a string specific to Savings accounts\n",
+ " def __str__(self):\n",
+ " return f'Savings Account #{self.acct_nbr}\\n Balance: {Account.__str__(self)}'\n",
+ "\n",
+ "\n",
+ "class Business(Account):\n",
+ " def __init__(self,acct_nbr,opening_deposit):\n",
+ " # Run the base class __init__\n",
+ " super().__init__(acct_nbr,opening_deposit)\n",
+ "\n",
+ " # Define a __str__ method that returns a string specific to Business accounts\n",
+ " def __str__(self):\n",
+ " return f'Business Account #{self.acct_nbr}\\n Balance: {Account.__str__(self)}'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**At this point** we've met the minimum requirement for the assignment. We have three different bank account classes. Each one can accept deposits, make withdrawals and report a balance, as they each inherit from an abstract Account base class.\n",
+ "\n",
+ "So now the fun part - let's add some features!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Step 5: Create a Customer class\n",
+ "\n",
+ "For this next phase, let's set up a Customer class that holds a customer's name and PIN and can contain any number and/or combination of Account objects."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Customer:\n",
+ " def __init__(self, name, PIN):\n",
+ " self.name = name\n",
+ " self.PIN = PIN\n",
+ " \n",
+ " # Create a dictionary of accounts, with lists to hold multiple accounts\n",
+ " self.accts = {'C':[],'S':[],'B':[]}\n",
+ " \n",
+ " def __str__(self):\n",
+ " return self.name\n",
+ " \n",
+ " def open_checking(self,acct_nbr,opening_deposit):\n",
+ " self.accts['C'].append(Checking(acct_nbr,opening_deposit))\n",
+ " \n",
+ " def open_savings(self,acct_nbr,opening_deposit):\n",
+ " self.accts['S'].append(Savings(acct_nbr,opening_deposit))\n",
+ " \n",
+ " def open_business(self,acct_nbr,opening_deposit):\n",
+ " self.accts['B'].append(Business(acct_nbr,opening_deposit))\n",
+ " \n",
+ " # rather than maintain a running total of deposit balances,\n",
+ " # write a method that computes a total as needed\n",
+ " def get_total_deposits(self):\n",
+ " total = 0\n",
+ " for acct in self.accts['C']:\n",
+ " print(acct)\n",
+ " total += acct.balance\n",
+ " for acct in self.accts['S']:\n",
+ " print(acct)\n",
+ " total += acct.balance\n",
+ " for acct in self.accts['B']:\n",
+ " print(acct)\n",
+ " total += acct.balance\n",
+ " print(f'Combined Deposits: ${total}')\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Step 6: TEST setting up a Customer, adding accounts, and checking balances"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "bob = Customer('Bob',1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "bob.open_checking(321,555.55)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Checking Account #321\n",
+ " Balance: $555.55\n",
+ "Combined Deposits: $555.55\n"
+ ]
+ }
+ ],
+ "source": [
+ "bob.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "bob.open_savings(564,444.66)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Checking Account #321\n",
+ " Balance: $555.55\n",
+ "Savings Account #564\n",
+ " Balance: $444.66\n",
+ "Combined Deposits: $1000.21\n"
+ ]
+ }
+ ],
+ "source": [
+ "bob.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "nancy = Customer('Nancy',2)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "nancy.open_business(2018,8900)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Business Account #2018\n",
+ " Balance: $8900.00\n",
+ "Combined Deposits: $8900\n"
+ ]
+ }
+ ],
+ "source": [
+ "nancy.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Wait!** Why don't Nancy's combined deposits show a decimal?
This is easily fixed in the class definition (mostly copied from above, with a change made to the last line of code):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Customer:\n",
+ " def __init__(self, name, PIN):\n",
+ " self.name = name\n",
+ " self.PIN = PIN\n",
+ " self.accts = {'C':[],'S':[],'B':[]}\n",
+ "\n",
+ " def __str__(self):\n",
+ " return self.name\n",
+ " \n",
+ " def open_checking(self,acct_nbr,opening_deposit):\n",
+ " self.accts['C'].append(Checking(acct_nbr,opening_deposit))\n",
+ " \n",
+ " def open_savings(self,acct_nbr,opening_deposit):\n",
+ " self.accts['S'].append(Savings(acct_nbr,opening_deposit))\n",
+ " \n",
+ " def open_business(self,acct_nbr,opening_deposit):\n",
+ " self.accts['B'].append(Business(acct_nbr,opening_deposit))\n",
+ " \n",
+ " def get_total_deposits(self):\n",
+ " total = 0\n",
+ " for acct in self.accts['C']:\n",
+ " print(acct)\n",
+ " total += acct.balance\n",
+ " for acct in self.accts['S']:\n",
+ " print(acct)\n",
+ " total += acct.balance\n",
+ " for acct in self.accts['B']:\n",
+ " print(acct)\n",
+ " total += acct.balance\n",
+ " print(f'Combined Deposits: ${total:.2f}') # added precision formatting here"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**So it's fixed, right?**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Business Account #2018\n",
+ " Balance: $8900.00\n",
+ "Combined Deposits: $8900\n"
+ ]
+ }
+ ],
+ "source": [
+ "nancy.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Nope!** Changes made to the class definition do *not* affect objects created under different sets of instructions.
To fix Nancy's account, we have to build her record from scratch."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Business Account #2018\n",
+ " Balance: $8900.00\n",
+ "Combined Deposits: $8900.00\n"
+ ]
+ }
+ ],
+ "source": [
+ "nancy = Customer('Nancy',2)\n",
+ "nancy.open_business(2018,8900)\n",
+ "nancy.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### This is why testing is so important!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Step 7: Let's write some functions for making deposits and withdrawals.\n",
+ "\n",
+ "Be sure to include a docstring that explains what's expected by the function!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def make_dep(cust,acct_type,acct_num,dep_amt):\n",
+ " \"\"\"\n",
+ " make_dep(cust, acct_type, acct_num, dep_amt)\n",
+ " cust = variable name (Customer record/ID)\n",
+ " acct_type = string 'C' 'S' or 'B'\n",
+ " acct_num = integer\n",
+ " dep_amt = integer\n",
+ " \"\"\"\n",
+ " for acct in cust.accts[acct_type]:\n",
+ " if acct.acct_nbr == acct_num:\n",
+ " acct.deposit(dep_amt)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "make_dep(nancy,'B',2018,67.45)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Business Account #2018\n",
+ " Balance: $8967.45\n",
+ "Combined Deposits: $8967.45\n"
+ ]
+ }
+ ],
+ "source": [
+ "nancy.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def make_wd(cust,acct_type,acct_num,wd_amt):\n",
+ " \"\"\"\n",
+ " make_dep(cust, acct_type, acct_num, wd_amt)\n",
+ " cust = variable name (Customer record/ID)\n",
+ " acct_type = string 'C' 'S' or 'B'\n",
+ " acct_num = integer\n",
+ " wd_amt = integer\n",
+ " \"\"\"\n",
+ " for acct in cust.accts[acct_type]:\n",
+ " if acct.acct_nbr == acct_num:\n",
+ " acct.withdraw(wd_amt)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "make_wd(nancy,'B',2018,1000000)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Business Account #2018\n",
+ " Balance: $8967.45\n",
+ "Combined Deposits: $8967.45\n"
+ ]
+ }
+ ],
+ "source": [
+ "nancy.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**What happened??** We seemed to successfully make a withdrawal, but nothing changed!
This is because, at the very beginning, we had our Account class *return* the string 'Funds Unavailable' instead of print it. If we change that here, we'll have to also run the derived class definitions, and Nancy's creation, but *not* the Customer class definition. Watch:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Account:\n",
+ " def __init__(self,acct_nbr,opening_deposit):\n",
+ " self.acct_nbr = acct_nbr\n",
+ " self.balance = opening_deposit\n",
+ " \n",
+ " def __str__(self):\n",
+ " return f'${self.balance:.2f}'\n",
+ " \n",
+ " def deposit(self,dep_amt):\n",
+ " self.balance += dep_amt\n",
+ " \n",
+ " def withdraw(self,wd_amt):\n",
+ " if self.balance >= wd_amt:\n",
+ " self.balance -= wd_amt\n",
+ " else:\n",
+ " print('Funds Unavailable') # changed \"return\" to \"print\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Checking(Account):\n",
+ " def __init__(self,acct_nbr,opening_deposit):\n",
+ " super().__init__(acct_nbr,opening_deposit)\n",
+ " \n",
+ " def __str__(self):\n",
+ " return f'Checking Account #{self.acct_nbr}\\n Balance: {Account.__str__(self)}'\n",
+ "\n",
+ " \n",
+ "class Savings(Account):\n",
+ " def __init__(self,acct_nbr,opening_deposit):\n",
+ " super().__init__(acct_nbr,opening_deposit)\n",
+ "\n",
+ " def __str__(self):\n",
+ " return f'Savings Account #{self.acct_nbr}\\n Balance: {Account.__str__(self)}'\n",
+ "\n",
+ "\n",
+ "class Business(Account):\n",
+ " def __init__(self,acct_nbr,opening_deposit):\n",
+ " super().__init__(acct_nbr,opening_deposit)\n",
+ "\n",
+ " def __str__(self):\n",
+ " return f'Business Account #{self.acct_nbr}\\n Balance: {Account.__str__(self)}'"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Business Account #2018\n",
+ " Balance: $8900.00\n",
+ "Combined Deposits: $8900.00\n"
+ ]
+ }
+ ],
+ "source": [
+ "nancy = Customer('Nancy',2)\n",
+ "nancy.open_business(2018,8900)\n",
+ "nancy.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Funds Unavailable\n"
+ ]
+ }
+ ],
+ "source": [
+ "make_wd(nancy,'B',2018,1000000)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Business Account #2018\n",
+ " Balance: $8900.00\n",
+ "Combined Deposits: $8900.00\n"
+ ]
+ }
+ ],
+ "source": [
+ "nancy.get_total_deposits()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Good job!"
+ ]
+ }
+ ],
+ "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.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/15-Advanced OOP/.ipynb_checkpoints/01-Advanced Object Oriented Programming-checkpoint.ipynb b/15-Advanced OOP/.ipynb_checkpoints/01-Advanced Object Oriented Programming-checkpoint.ipynb
new file mode 100644
index 000000000..bb5d231df
--- /dev/null
+++ b/15-Advanced OOP/.ipynb_checkpoints/01-Advanced Object Oriented Programming-checkpoint.ipynb
@@ -0,0 +1,478 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "collapsed": true
+ },
+ "source": [
+ "# Advanced Object Oriented Programming\n",
+ "\n",
+ "\n",
+ "In the regular section on Object Oriented Programming (OOP) we covered:\n",
+ "\n",
+ "* Using the *class* keyword to define object classes\n",
+ "* Creating class attributes\n",
+ "* Creating class methods\n",
+ "* Inheritance - where derived classes can inherit attributes and methods from a base class\n",
+ "* Polymorphism - where different object classes that share the same method can be called from the same place\n",
+ "* Special Methods for classes like `__init__`, `__str__`, `__len__` and `__del__`\n",
+ "\n",
+ "In this section we'll dive deeper into\n",
+ "* Multiple Inheritance\n",
+ "* The `self` keyword\n",
+ "* Method Resolution Order (MRO)\n",
+ "* Python's built-in `super()` function"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Inheritance Revisited\n",
+ "\n",
+ "Recall that with Inheritance, one or more derived classes can inherit attributes and methods from a base class. This reduces duplication, and means that any changes made to the base class will automatically translate to derived classes. As a review:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Fido says Woof!\n",
+ "Isis says Meow!\n"
+ ]
+ }
+ ],
+ "source": [
+ "class Animal:\n",
+ " def __init__(self, name): # Constructor of the class\n",
+ " self.name = name\n",
+ "\n",
+ " def speak(self): # Abstract method, defined by convention only\n",
+ " raise NotImplementedError(\"Subclass must implement abstract method\")\n",
+ "\n",
+ "\n",
+ "class Dog(Animal):\n",
+ " def speak(self):\n",
+ " return self.name+' says Woof!'\n",
+ " \n",
+ "class Cat(Animal):\n",
+ " def speak(self):\n",
+ " return self.name+' says Meow!'\n",
+ " \n",
+ "fido = Dog('Fido')\n",
+ "isis = Cat('Isis')\n",
+ "\n",
+ "print(fido.speak())\n",
+ "print(isis.speak())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In this example, the derived classes did not need their own `__init__` methods because the base class `__init__` gets called automatically. However, if you do define an `__init__` in the derived class, this will override the base:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Animal:\n",
+ " def __init__(self,name,legs):\n",
+ " self.name = name\n",
+ " self.legs = legs\n",
+ "\n",
+ "class Bear(Animal):\n",
+ " def __init__(self,name,legs=4,hibernate='yes'):\n",
+ " self.name = name\n",
+ " self.legs = legs\n",
+ " self.hibernate = hibernate\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This is inefficient - why inherit from Animal if we can't use its constructor? The answer is to call the Animal `__init__` inside our own `__init__`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Yogi\n",
+ "4\n",
+ "yes\n"
+ ]
+ }
+ ],
+ "source": [
+ "class Animal:\n",
+ " def __init__(self,name,legs):\n",
+ " self.name = name\n",
+ " self.legs = legs\n",
+ "\n",
+ "class Bear(Animal):\n",
+ " def __init__(self,name,legs=4,hibernate='yes'):\n",
+ " Animal.__init__(self,name,legs)\n",
+ " self.hibernate = hibernate\n",
+ " \n",
+ "yogi = Bear('Yogi')\n",
+ "print(yogi.name)\n",
+ "print(yogi.legs)\n",
+ "print(yogi.hibernate)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Multiple Inheritance\n",
+ "\n",
+ "Sometimes it makes sense for a derived class to inherit qualities from two or more base classes. Python allows for this with multiple inheritance."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Car:\n",
+ " def __init__(self,wheels=4):\n",
+ " self.wheels = wheels\n",
+ " # We'll say that all cars, no matter their engine, have four wheels by default.\n",
+ "\n",
+ "class Gasoline(Car):\n",
+ " def __init__(self,engine='Gasoline',tank_cap=20):\n",
+ " Car.__init__(self)\n",
+ " self.engine = engine\n",
+ " self.tank_cap = tank_cap # represents fuel tank capacity in gallons\n",
+ " self.tank = 0\n",
+ " \n",
+ " def refuel(self):\n",
+ " self.tank = self.tank_cap\n",
+ " \n",
+ " \n",
+ "class Electric(Car):\n",
+ " def __init__(self,engine='Electric',kWh_cap=60):\n",
+ " Car.__init__(self)\n",
+ " self.engine = engine\n",
+ " self.kWh_cap = kWh_cap # represents battery capacity in kilowatt-hours\n",
+ " self.kWh = 0\n",
+ " \n",
+ " def recharge(self):\n",
+ " self.kWh = self.kWh_cap"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "So what happens if we have an object that shares properties of both Gasolines and Electrics? We can create a derived class that inherits from both!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "0\n",
+ "0\n"
+ ]
+ }
+ ],
+ "source": [
+ "class Hybrid(Gasoline, Electric):\n",
+ " def __init__(self,engine='Hybrid',tank_cap=11,kWh_cap=5):\n",
+ " Gasoline.__init__(self,engine,tank_cap)\n",
+ " Electric.__init__(self,engine,kWh_cap)\n",
+ " \n",
+ " \n",
+ "prius = Hybrid()\n",
+ "print(prius.tank)\n",
+ "print(prius.kWh)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "5\n"
+ ]
+ }
+ ],
+ "source": [
+ "prius.recharge()\n",
+ "print(prius.kWh)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Why do we use `self`?\n",
+ "\n",
+ "We've seen the word \"self\" show up in almost every example. What's the deal? The answer is, Python uses `self` to find the right set of attributes and methods to apply to an object. When we say:\n",
+ "\n",
+ " prius.recharge()\n",
+ "\n",
+ "What really happens is that Python first looks up the class belonging to `prius` (Hybrid), and then passes `prius` to the `Hybrid.recharge()` method.\n",
+ "\n",
+ "It's the same as running:\n",
+ "\n",
+ " Hybrid.recharge(prius)\n",
+ " \n",
+ "but shorter and more intuitive!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Method Resolution Order (MRO)\n",
+ "Things get complicated when you have several base classes and levels of inheritance. This is resolved using Method Resolution Order - a formal plan that Python follows when running object methods.\n",
+ "\n",
+ "To illustrate, if classes B and C each derive from A, and class D derives from both B and C, which class is \"first in line\" when a method is called on D?
Consider the following:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class A:\n",
+ " num = 4\n",
+ " \n",
+ "class B(A):\n",
+ " pass\n",
+ "\n",
+ "class C(A):\n",
+ " num = 5\n",
+ " \n",
+ "class D(B,C):\n",
+ " pass"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Schematically, the relationship looks like this:\n",
+ "\n",
+ "\n",
+ " A\n",
+ " num=4\n",
+ " / \\\n",
+ " / \\\n",
+ " B C\n",
+ " pass num=5\n",
+ " \\ /\n",
+ " \\ /\n",
+ " D\n",
+ " pass\n",
+ "\n",
+ "Here `num` is a class attribute belonging to all four classes. So what happens if we call `D.num`?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "5"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "D.num"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "You would think that `D.num` would follow `B` up to `A` and return **4**. Instead, Python obeys the first method in the chain that *defines* num. The order followed is `[D, B, C, A, object]` where *object* is Python's base object class.\n",
+ "\n",
+ "In our example, the first class to define and/or override a previously defined `num` is `C`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## `super()`\n",
+ "\n",
+ "Python's built-in `super()` function provides a shortcut for calling base classes, because it automatically follows Method Resolution Order.\n",
+ "\n",
+ "In its simplest form with single inheritance, `super()` can be used in place of the base class name :"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class MyBaseClass:\n",
+ " def __init__(self,x,y):\n",
+ " self.x = x\n",
+ " self.y = y\n",
+ " \n",
+ "class MyDerivedClass(MyBaseClass):\n",
+ " def __init__(self,x,y,z):\n",
+ " super().__init__(x,y)\n",
+ " self.z = z\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Note that we don't pass `self` to `super().__init__()` as `super()` handles this automatically.\n",
+ "\n",
+ "In a more dynamic form, with multiple inheritance like the \"diamond diagram\" shown above, `super()` can be used to properly manage method definitions:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class A:\n",
+ " def truth(self):\n",
+ " return 'All numbers are even'\n",
+ " \n",
+ "class B(A):\n",
+ " pass\n",
+ "\n",
+ "class C(A):\n",
+ " def truth(self):\n",
+ " return 'Some numbers are even'\n",
+ " \n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'All numbers are even'"
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "class D(B,C):\n",
+ " def truth(self,num):\n",
+ " if num%2 == 0:\n",
+ " return A.truth(self)\n",
+ " else:\n",
+ " return super().truth()\n",
+ " \n",
+ "d = D()\n",
+ "d.truth(6)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'Some numbers are even'"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "d.truth(5)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In the above example, if we pass an even number to `d.truth()`, we'll believe the `A` version of `.truth()` and run with it. Otherwise, follow the MRO and return the more general case."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "For more information on `super()` visit https://docs.python.org/3/library/functions.html#super
and https://rhettinger.wordpress.com/2011/05/26/super-considered-super/"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Great! Now you should have a much deeper understanding of Object Oriented Programming!"
+ ]
+ }
+ ],
+ "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.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
diff --git a/15-Advanced OOP/01-Advanced Object Oriented Programming.ipynb b/15-Advanced OOP/01-Advanced Object Oriented Programming.ipynb
index 47adc07f5..bb5d231df 100644
--- a/15-Advanced OOP/01-Advanced Object Oriented Programming.ipynb
+++ b/15-Advanced OOP/01-Advanced Object Oriented Programming.ipynb
@@ -351,7 +351,7 @@
" \n",
"class MyDerivedClass(MyBaseClass):\n",
" def __init__(self,x,y,z):\n",
- " super().__init(x,y)\n",
+ " super().__init__(x,y)\n",
" self.z = z\n",
" "
]