<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Lecture 9
Object Oriented Programming
Monday, October 2nd 2017
Step1:  Motiviation
We would like to find a way to represent complex, structured data in the context of our programming language.
For example, to represent a location, we might want to associate a name, a latitude and a longitude with it. 
Thus we would want to create a compound data type which carries this information.
In C, for example, this is a struct
Step2:  But things aren't hidden so I can get through the interface
Step3:  Because I used a tuple, and a tuple is immutable, I can't change this complex number once it's created.
Step4:  Objects thru closures
Let's try an implementation that uses a closure to capture the value of arguments.
Step5:  This looks pretty good so far.
The only problem is that we don't have a way to change the real and imaginary parts.
For this, we need to add things called setters.
Objects with Setters
Step6:  Python Classes and instance variables
We constructed an object system above. But Python comes with its own.
Classes allow us to define our own types in the Python type system.
Step7:  __init__ is a special method run automatically by Python.
It is a constructor.
self is the instance of the object.
It acts like this in C++ but self is explicit.
Step8:  Inheritance and Polymorphism
Inheritance
Inheritance is the idea that a "Cat" is-a "Animal" and a "Dog" is-a "Animal". 
Animals make sounds, but Cats Meow and Dogs Bark.
Inheritance makes sure that methods not defined in a child are found and used from a parent.
Polymorphism
Polymorphism is the idea that an interface is specified, but not necessarily implemented, by a superclass and then the interface is implemented in subclasses (differently).
[Actually Polymorphism is much more complex and interesting than this, and this definition is really an outcome of polymorphism. But we'll come to this later.]
Example
Step9:  Animal is the superclass (a.k.a the base class).
Dog and Cat are both subclasses (a.k.a derived classes) of the Animal superclass.
Using the Animal class
Step10:  How does this all work?
Step11:  Calling a superclasses initializer
Say we dont want to do all the work of setting the name variable in the subclasses.
We can set this "common" work up in the superclass and use super to call the superclass's initializer from the subclass.
There's another way to think about this
Step12:  Interfaces
The above examples show inheritance and polymorphism.
Notice that we didn't actually need to set up the inheritance.
We could have just defined 2 different classes and have them both make_sound.
In Java and C++ this is done more formally through Interfaces and  Abstract Base Classes, respectively, plus inheritance.
In Python, this agreement to define make_sound is called duck typing.
"If it walks like a duck and quacks like a duck, it is a duck."
Step13:  The Python Data Model
Duck typing is used throughout Python. Indeed it's what enables the "Python Data Model" 
All python classes implicitly inherit from the root object class.
The Pythonic way is to just document your interface and implement it. 
This usage of common interfaces is pervasive in dunder functions to comprise the Python data model.
Example
<ASSISTANT_TASK:>
Python Code:
from IPython.display import HTML
Explanation: Lecture 9
Object Oriented Programming
Monday, October 2nd 2017
End of explanation
def Complex(a, b): # constructor
    return (a,b)
def real(c): # method
    return c[0]
def imag(c):
    return c[1]
def str_complex(c):
    return "{0}+{1}i".format(c[0], c[1])
c1 = Complex(1,2) # constructor
print(real(c1), "     ", str_complex(c1))
Explanation: Motiviation
We would like to find a way to represent complex, structured data in the context of our programming language.
For example, to represent a location, we might want to associate a name, a latitude and a longitude with it. 
Thus we would want to create a compound data type which carries this information.
In C, for example, this is a struct:
C
struct location {
    float longitude;
    float latitude;
}
REMEMBER: A language has 3 parts:
expressions and statements: how to structure simple computations
means of combination: how to structure complex computations
means of abstraction: how to build complex units
Review
When we write a function, we give it some sensible name which can then be used by a "client" programmer. We don't care about how this function is implemented.  We just want to know its signature (API) and use it.
In a similar way, we want to encapsulate our data: we dont want to know how it is stored and all that.  We just want to be able to use it. This is one of the key ideas behind object oriented programming. 
To do this, write constructors that make objects.  We also write other functions that access or change data on the object. These functions are called the "methods" of the object, and are what the client programmer uses.
First Examples
Objects thru tuples:  An object for complex numbers
How might we implement such objects? First, lets think of tuples.
End of explanation
c1[0]
Explanation: But things aren't hidden so I can get through the interface:
End of explanation
c1[0]=2
Explanation: Because I used a tuple, and a tuple is immutable, I can't change this complex number once it's created.
End of explanation
def Complex2(a, b): # constructor
    def dispatch(message): # capture a and b at constructor-run time
        if message=="real":
            return a
        elif message=='imag':
            return b
        elif message=="str":
            return "{0}+{1}i".format(a, b)
    return dispatch
z=Complex2(1,2)
print(z("real"), "     ", z("imag"), "     ", z("str"))
Explanation: Objects thru closures
Let's try an implementation that uses a closure to capture the value of arguments.
End of explanation
def Complex3(a, b):
    in_a=a
    in_b=b
    def dispatch(message, value=None):
        nonlocal in_a, in_b
        if message=='set_real' and value != None:
            in_a = value
        elif message=='set_imag' and value != None:
            in_b = value
        elif message=="real":
            return in_a
        elif message=='imag':
            return in_b
        elif message=="str":
            return "{0}+{1}i".format(in_a, in_b)
    return dispatch
c3=Complex3(1,2)
print(c3("real"), "     ", c3("imag"), "     ", c3("str"))
c3('set_real', 2)
print(c3("real"), "     ", c3("imag"), "     ", c3("str"))
Explanation: This looks pretty good so far.
The only problem is that we don't have a way to change the real and imaginary parts.
For this, we need to add things called setters.
Objects with Setters
End of explanation
class ComplexClass():
    
    def __init__(self, a, b):
        self.real = a
        self.imaginary = b
Explanation: Python Classes and instance variables
We constructed an object system above. But Python comes with its own.
Classes allow us to define our own types in the Python type system.
End of explanation
HTML('<iframe width="800" height="500" frameborder="0" src="http://pythontutor.com/iframe-embed.html#code=class%20ComplexClass%28%29%3A%0A%20%20%20%20%0A%20%20%20%20def%20__init__%28self,%20a,%20b%29%3A%0A%20%20%20%20%20%20%20%20self.real%20%3D%20a%0A%20%20%20%20%20%20%20%20self.imaginary%20%3D%20b%0A%0Ac1%20%3D%20ComplexClass%281,2%29&codeDivHeight=400&codeDivWidth=350&cumulative=false&curInstr=0&heapPrimitives=false&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe>')
c1 = ComplexClass(1,2)
print(c1, c1.real)
print(vars(c1), "     ",type(c1))
c1.real=5.0
print(c1, "     ", c1.real, "     ", c1.imaginary)
Explanation: __init__ is a special method run automatically by Python.
It is a constructor.
self is the instance of the object.
It acts like this in C++ but self is explicit.
End of explanation
class Animal():
    
    def __init__(self, name):
        self.name = name
        
    def make_sound(self):
        raise NotImplementedError
    
class Dog(Animal):
    
    def make_sound(self):
        return "Bark"
    
class Cat(Animal):
    
    def __init__(self, name):
        self.name = "A very interesting cat: {}".format(name)
        
    def make_sound(self):
        return "Meow"
Explanation: Inheritance and Polymorphism
Inheritance
Inheritance is the idea that a "Cat" is-a "Animal" and a "Dog" is-a "Animal". 
Animals make sounds, but Cats Meow and Dogs Bark.
Inheritance makes sure that methods not defined in a child are found and used from a parent.
Polymorphism
Polymorphism is the idea that an interface is specified, but not necessarily implemented, by a superclass and then the interface is implemented in subclasses (differently).
[Actually Polymorphism is much more complex and interesting than this, and this definition is really an outcome of polymorphism. But we'll come to this later.]
Example:  Super- and subclasses
End of explanation
a0 = Animal("David")
print(a0.name)
a0.make_sound()
a1 = Dog("Snoopy")
a2 = Cat("Hello Kitty")
animals = [a1, a2]
for a in animals:
    print(a.name)
    print(isinstance(a, Animal))
    print(a.make_sound())
    print('--------')
print(a1.make_sound, "     ", Dog.make_sound)
print(a1.make_sound())
print('----')
print(Dog.make_sound(a1))
Dog.make_sound()
Explanation: Animal is the superclass (a.k.a the base class).
Dog and Cat are both subclasses (a.k.a derived classes) of the Animal superclass.
Using the Animal class
End of explanation
HTML('<iframe width="800" height="500" frameborder="0" src="http://pythontutor.com/iframe-embed.html#code=class%20Animal%28%29%3A%0A%20%20%20%20%0A%20%20%20%20def%20__init__%28self,%20name%29%3A%0A%20%20%20%20%20%20%20%20self.name%20%3D%20name%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20def%20make_sound%28self%29%3A%0A%20%20%20%20%20%20%20%20raise%20NotImplementedError%0A%20%20%20%20%0Aclass%20Dog%28Animal%29%3A%0A%20%20%20%20%0A%20%20%20%20def%20make_sound%28self%29%3A%0A%20%20%20%20%20%20%20%20return%20%22Bark%22%0A%20%20%20%20%0Aclass%20Cat%28Animal%29%3A%0A%20%20%20%20%0A%20%20%20%20def%20__init__%28self,%20name%29%3A%0A%20%20%20%20%20%20%20%20self.name%20%3D%20%22A%20very%20interesting%20cat%3A%20%7B%7D%22.format%28name%29%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20def%20make_sound%28self%29%3A%0A%20%20%20%20%20%20%20%20return%20%22Meow%22%0A%0Aa1%20%3D%20Dog%28%22Snoopy%22%29%0Aa2%20%3D%20Cat%28%22Hello%20Kitty%22%29%0Aanimals%20%3D%20%5Ba1,%20a2%5D%0Afor%20a%20in%20animals%3A%0A%20%20%20%20print%28a.name%29%0A%20%20%20%20print%28isinstance%28a,%20Animal%29%29%0A%20%20%20%20print%28a.make_sound%28%29%29%0A%20%20%20%20print%28\'--------\'%29&codeDivHeight=400&codeDivWidth=350&cumulative=false&curInstr=0&heapPrimitives=false&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe>')
Explanation: How does this all work?
End of explanation
class Animal():
    def __init__(self, name):
        self.name=name
        print("Name is", self.name)
        
class Mouse(Animal):
    def __init__(self, name):
        self.animaltype="prey"
        super().__init__(name)
        print("Created %s as %s" % (self.name, self.animaltype))
    
class Cat(Animal):
    pass
a1 = Mouse("Tom")
print(vars(a1))
a2 = Cat("Jerry")
print(vars(a2))
HTML('<iframe width="800" height="500" frameborder="0" src="http://pythontutor.com/iframe-embed.html#code=class%20Animal%28%29%3A%0A%20%20%20%20%0A%20%20%20%20def%20__init__%28self,%20name%29%3A%0A%20%20%20%20%20%20%20%20self.name%3Dname%0A%20%20%20%20%20%20%20%20print%28%22Name%20is%22,%20self.name%29%0A%20%20%20%20%20%20%20%20%0Aclass%20Mouse%28Animal%29%3A%0A%20%20%20%20def%20__init__%28self,%20name%29%3A%0A%20%20%20%20%20%20%20%20self.animaltype%3D%22prey%22%0A%20%20%20%20%20%20%20%20super%28%29.__init__%28name%29%0A%20%20%20%20%20%20%20%20print%28%22Created%20%25s%20as%20%25s%22%20%25%20%28self.name,%20self.animaltype%29%29%0A%20%20%20%20%0Aclass%20Cat%28Animal%29%3A%0A%20%20%20%20pass%0A%0Aa1%20%3D%20Mouse%28%22Tom%22%29%0Aa2%20%3D%20Cat%28%22Jerry%22%29&codeDivHeight=400&codeDivWidth=350&cumulative=false&curInstr=0&heapPrimitives=false&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe>')
Explanation: Calling a superclasses initializer
Say we dont want to do all the work of setting the name variable in the subclasses.
We can set this "common" work up in the superclass and use super to call the superclass's initializer from the subclass.
There's another way to think about this:
A subclass method will be called instead of a superclass method if the method is in both the sub- and superclass and we call the subclass (polymorphism!).
If we really want the superclass method, then we can use the super built-in function.
See https://rhettinger.wordpress.com/2011/05/26/super-considered-super/
End of explanation
# Both implement the "Animal" Protocol, which consists of the one make_sound function
class Dog():
    
    def make_sound(self):
        return "Bark"
    
class Cat():
    
    def make_sound(self):
        return "Meow"  
    
a1 = Dog()
a2 = Cat()
animals = [a1, a2]
for a in animals:
    print(isinstance(a, Animal), "     ", a.make_sound())
Explanation: Interfaces
The above examples show inheritance and polymorphism.
Notice that we didn't actually need to set up the inheritance.
We could have just defined 2 different classes and have them both make_sound.
In Java and C++ this is done more formally through Interfaces and  Abstract Base Classes, respectively, plus inheritance.
In Python, this agreement to define make_sound is called duck typing.
"If it walks like a duck and quacks like a duck, it is a duck."
End of explanation
class Animal():
    
    def __init__(self, name):
        self.name=name
        
    def __repr__(self):
        class_name = type(self).__name__
        return "{0!s}({1.name!r})".format(class_name, self)
r = Animal("David")
r
print(r)
repr(r)
Explanation: The Python Data Model
Duck typing is used throughout Python. Indeed it's what enables the "Python Data Model" 
All python classes implicitly inherit from the root object class.
The Pythonic way is to just document your interface and implement it. 
This usage of common interfaces is pervasive in dunder functions to comprise the Python data model.
Example:  Printing with __repr__ and __str__
The way printing works is that Python wants classes to implement __repr__ and __str__ methods. 
It will use inheritance to give the built-in objects methods when these are not defined.
Any class can define __repr__ and __str__. 
When an instance of such a class is interrogated with the repr or str function, then these underlying methods are called.
We'll see __repr__ here. If you define __repr__ you have made an object sensibly printable.
__repr__
End of explanation
<END_TASK>