<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 The Class Structure in Python
Lesson Page
Step1:  The attributes in Client are name, balance and level. 
Note
Step2:  We can see the attributes of John_Doe, or Jane_Defoe by calling them
Step3:  We can also add, remove or modify attributes as we like
Step4:  You can also use the following instead instead of the normal statements
Step5:  Methods
Methods are functions that can be applied (only) to instances of your class. 
For example, in the case of our 'Client' class, we may want to update a person's bank account once they withdraw or deposit money. Let's create these methods below. 
Note that each method takes 'self' as an argument along with the arguments required when calling this method.
Step6:  What is "self"?
*not in the philosophical sense*
In the method, withdraw(self, amount), the self refers to the instance upon which we are applying the instructions of the method. 
When we call a method, f(self, arg), on the object x, we use x.f(arg).
- x is passed as the first argument, self, by default and all that is required are the other arguments that comprise the function. 
It is equivalent to calling MyClass.f(x, arg).
Try it yourself with the Client class and one of the methods we've written.
Step7:  Static Methods
Static methods are methods that belong to a class but do not have access to self and hence don't require an instance to function (i.e. it will work on the class level as well as the instance level). 
We denote these with the line @staticmethod before we define our static method.
Let's create a static method called make_money_sound() that will simply print "Cha-ching!" when called.
Step8:  Class Methods
A class method is a type of method that will receive the class rather than the instance as the first parameter. It is also identified similarly to a static method, with @classmethod.
Create a class method called bank_location() that will print both the bank name and location when called upon the class.
Step9:  Key Concept
<ASSISTANT_TASK:>
Python Code:
# create the Client class below
class Client(object):
    def __init__(self, name, balance):
        self.name = name
        self.balance = balance + 100
        
        #define account level
        if self.balance < 5000:
            self.level = "Basic"
        elif self.balance < 15000:
            self.level = "Intermediate"
        else:
            self.level = "Advanced"
            
            
Explanation: The Class Structure in Python
Lesson Page: https://github.com/UofTCoders/studyGroup/blob/gh-pages/lessons/pythonClasses/lesson.md
Adapted from https://www.jeffknupp.com/blog/2014/06/18/improve-your-python-python-classes-and-object-oriented-programming/
What is a Class?
A class is a structure in Python that can be used as a blueprint to create objects that have 
1. prototyped features, "attributes" that are variable
2. "methods" which are functions that can be applied to the object that is created, or rather, an instance of that class. 
Defining a Class
We want to define a class called Client in which a new instance stores a client's name, balance, and account level.
It will take the format of:
class Client(object):
    def __init__(self, args[, ...])
        #more code
"def __init__" is what we use when creating classes to define how we can create a new instance of this class. 
The arguments of __init__ are required input when creating a new instance of this class, except for 'self'.
End of explanation
John_Doe = Client("John Doe", 500)
Jane_Defoe = Client("Jane Defoe", 150000)
Explanation: The attributes in Client are name, balance and level. 
Note: "self.name" and "name" are different variables. Here they represent the same values, but in other cases, this may lead to problems. For example, here the bank has decided to update "self.balance" by giving all new members a bonus $100 on top of what they're putting in the bank. Calling "balance" for other calculations will not have the correct value.
Creating an Instance of a Class
Now, lets try creating some new clients named John_Doe, and Jane_Defoe:
End of explanation
John_Doe.name
Jane_Defoe.level
Jane_Defoe.balance
Explanation: We can see the attributes of John_Doe, or Jane_Defoe by calling them:
End of explanation
John_Doe.email = "jdoe23@gmail.com"
John_Doe.email = "johndoe23@gmail.com"
del John_Doe.email
getattr(John_Doe, 'name')
setattr(John_Doe, 'email', 'jdoe23@gmail.com')
John_Doe.email
Explanation: We can also add, remove or modify attributes as we like:
End of explanation
Client.bank = "TD"
Client.location = "Toronto, ON"
# try calling these attributes at the class and instance level
Client.bank
Jane_Defoe.bank
Explanation: You can also use the following instead instead of the normal statements:
The getattr(obj, name[, default]) : to access the attribute of object.
The hasattr(obj,name) : to check if an attribute exists or not.
The setattr(obj,name,value) : to set an attribute. If attribute does not exist, then it would be created.
The delattr(obj, name) : to delete an attribute.
Class Attributes vs. Normal Attributes
A class attribute is an attribute set at the class-level rather than the instance-level, such that the value of this attribute will be the same across all instances.
For our Client class, we might want to set the name of the bank, and the location, which would not change from instance to instance.
End of explanation
# Use the Client class code above to now add methods for withdrawal and depositing of money
# create the Client class below
class Client(object):
    def __init__(self, name, balance):
        self.name = name
        self.balance = balance + 100
        
        #define account level
        if self.balance < 5000:
            self.level = "Basic"
        elif self.balance < 15000:
            self.level = "Intermediate"
        else:
            self.level = "Advanced"
            
    def deposit(self, amount):
        self.balance += amount
        return self.balance
    
    def withdraw(self, amount):
        if amount > self.balance:
            raise RuntimeError("Insufficient for withdrawal")
        else:
            self.balance -= amount
        return self.balance
    
    
Jane_Defoe.deposit(150000)
Explanation: Methods
Methods are functions that can be applied (only) to instances of your class. 
For example, in the case of our 'Client' class, we may want to update a person's bank account once they withdraw or deposit money. Let's create these methods below. 
Note that each method takes 'self' as an argument along with the arguments required when calling this method.
End of explanation
# Try calling a method two different ways
John_Doe.deposit(500)
Client.withdraw(Jane_Defoe, 50000)
Explanation: What is "self"?
*not in the philosophical sense*
In the method, withdraw(self, amount), the self refers to the instance upon which we are applying the instructions of the method. 
When we call a method, f(self, arg), on the object x, we use x.f(arg).
- x is passed as the first argument, self, by default and all that is required are the other arguments that comprise the function. 
It is equivalent to calling MyClass.f(x, arg).
Try it yourself with the Client class and one of the methods we've written.
End of explanation
# Add a static method called make_money_sound()
# create the Client class below
class Client(object):
    def __init__(self, name, balance):
        self.name = name
        self.balance = balance + 100
        
        #define account level
        if self.balance < 5000:
            self.level = "Basic"
        elif self.balance < 15000:
            self.level = "Intermediate"
        else:
            self.level = "Advanced"
            
    @staticmethod
    def make_money_sound():
        print "Cha-ching!"
        
    
Client.make_money_sound()
Explanation: Static Methods
Static methods are methods that belong to a class but do not have access to self and hence don't require an instance to function (i.e. it will work on the class level as well as the instance level). 
We denote these with the line @staticmethod before we define our static method.
Let's create a static method called make_money_sound() that will simply print "Cha-ching!" when called.
End of explanation
# Add a class method called bank_location()
# create the Client class below
class Client(object):
    bank = "TD"
    location = "Toronto, ON"
    
    def __init__(self, name, balance):
        self.name = name
        self.balance = balance + 100
        
        #define account level
        if self.balance < 5000:
            self.level = "Basic"
        elif self.balance < 15000:
            self.level = "Intermediate"
        else:
            self.level = "Advanced"
            
    @classmethod
    def bank_location(cls):
        return str(cls.bank + " " + cls.location)
Client.bank_location()
Explanation: Class Methods
A class method is a type of method that will receive the class rather than the instance as the first parameter. It is also identified similarly to a static method, with @classmethod.
Create a class method called bank_location() that will print both the bank name and location when called upon the class.
End of explanation
# create the Savings class below
class Savings(Client):
    interest_rate = 0.005
    
    def update_balance(self):
        self.balance += self.balance*self.interest_rate
        return self.balance
# create an instance the same way as a Client but this time by calling Savings instead
Lina_Tran = Savings("Lina Tran", 50)
# it now has access to the new attributes and methods in Savings...
print Lina_Tran.name
print Lina_Tran.balance
print Lina_Tran.interest_rate
# ...as well as access to attributes and methods from the Client class as well
Lina_Tran.update_balance()
#defining a method outside the class definition
def check_balance(self):
    return self.balance
Client.check_balance = check_balance
John_Doe.check_balance()
Explanation: Key Concept: Inheritance
A 'child' class can be created from a 'parent' class, whereby the child will bring over attributes and methods that its parent has, but where new features can be created as well. 
This would be useful if you want to create multiple classes that would have some features that are kept the same between them. You would simply create a parent class of these children classes that have those maintained features.
Imagine we want to create different types of clients but still have all the base attributes and methods found in client currently. 
For example, let's create a class called Savings that inherits from the Client class. In doing so, we do not need to write another __init__ method as it will inherit this from its parent.
End of explanation
<END_TASK>