We now return to use the bank account example from the previous section. Using our implemented object system, we will create an Account class, a CheckingAccount subclass, and an instance of each.
    The Account class is created through a make_account_class function, which has structure similar to a class statement in Python, but concludes with a call to make_class.

    1. >>> def make_account_class():
    2. """Return the Account class, which has deposit and withdraw methods."""
    3. interest = 0.02
    4. def __init__(self, account_holder):
    5. self['set']('holder', account_holder)
    6. self['set']('balance', 0)
    7. def deposit(self, amount):
    8. """Increase the account balance by amount and return the new balance."""
    9. new_balance = self['get']('balance') + amount
    10. self['set']('balance', new_balance)
    11. return self['get']('balance')
    12. def withdraw(self, amount):
    13. """Decrease the account balance by amount and return the new balance."""
    14. balance = self['get']('balance')
    15. if amount > balance:
    16. return 'Insufficient funds'
    17. self['set']('balance', balance - amount)
    18. return self['get']('balance')
    19. return make_class(locals())

    The final call to locals returns a dictionary with string keys that contains the name-value bindings in the current local frame.
    The Account class is finally instantiated via assignment.

    1. >>> Account = make_account_class()

    Then, an account instance is created via the new message, which requires a name to go with the newly created account.

    1. >>> kirk_account = Account['new']('Kirk')

    Then, get messages passed to kirk_account retrieve properties and methods. Methods can be called to update the balance of the account.

    1. >>> kirk_account['get']('holder')
    2. 'Kirk'
    3. >>> kirk_account['get']('interest')
    4. 0.02
    5. >>> kirk_account['get']('deposit')(20)
    6. 20
    7. >>> kirk_account['get']('withdraw')(5)
    8. 15

    As with the Python object system, setting an attribute of an instance does not change the corresponding attribute of its class.

    1. >>> kirk_account['set']('interest', 0.04)
    2. >>> Account['get']('interest')
    3. 0.02

    Inheritance. We can create a subclass CheckingAccount by overloading a subset of the class attributes. In this case, we change the withdraw method to impose a fee, and we reduce the interest rate.

    1. >>> def make_checking_account_class():
    2. """Return the CheckingAccount class, which imposes a $1 withdrawal fee."""
    3. interest = 0.01
    4. withdraw_fee = 1
    5. def withdraw(self, amount):
    6. fee = self['get']('withdraw_fee')
    7. return Account['get']('withdraw')(self, amount + fee)
    8. return make_class(locals(), Account)

    In this implementation, we call the withdraw function of the base class Account from the withdraw function of the subclass, as we would in Python’s built-in object system. We can create the subclass itself and an instance, as before.

    >>> CheckingAccount = make_checking_account_class()
    >>> jack_acct = CheckingAccount['new']('Spock')
    

    Deposits behave identically, as does the constructor function. withdrawals impose the $1 fee from the specialized withdraw method, and interest has the new lower value from CheckingAccount.

    >>> jack_acct['get']('interest')
    0.01
    >>> jack_acct['get']('deposit')(20)
    20
    >>> jack_acct['get']('withdraw')(5)
    14
    

    Our object system built upon dictionaries is quite similar in implementation to the built-in object system in Python. In Python, an instance of any user-defined class has a special attribute dict that stores the local instance attributes for that object in a dictionary, much like our attributes dictionary. Python differs because it distinguishes certain special methods that interact with built-in functions to ensure that those functions behave correctly for arguments of many different types. Functions that operate on different types are the subject of the next section.