In [1]:
from IPython.display import HTML
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:
struct location {
float longitude;
float latitude;
}
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.
In [2]:
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])
In [3]:
c1 = Complex(1,2) # constructor
print(real(c1), " ", str_complex(c1))
But things aren't hidden so I can get through the interface:
In [4]:
c1[0]
Out[4]:
Because I used a tuple, and a tuple is immutable, I can't change this complex number once it's created.
In [5]:
c1[0]=2
In [6]:
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
In [7]:
z=Complex2(1,2)
print(z("real"), " ", z("imag"), " ", z("str"))
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.
In [8]:
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
In [9]:
c3=Complex3(1,2)
print(c3("real"), " ", c3("imag"), " ", c3("str"))
In [10]:
c3('set_real', 2)
In [11]:
print(c3("real"), " ", c3("imag"), " ", c3("str"))
In [12]:
class ComplexClass():
def __init__(self, a, b):
self.real = a
self.imaginary = b
__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.
In [13]:
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>')
Out[13]:
In [14]:
c1 = ComplexClass(1,2)
print(c1, c1.real)
In [15]:
print(vars(c1), " ",type(c1))
In [16]:
c1.real=5.0
print(c1, " ", c1.real, " ", c1.imaginary)
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.]
In [17]:
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"
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.
In [18]:
a0 = Animal("David")
print(a0.name)
a0.make_sound()
In [19]:
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('--------')
In [20]:
print(a1.make_sound, " ", Dog.make_sound)
In [21]:
print(a1.make_sound())
print('----')
print(Dog.make_sound(a1))
In [22]:
Dog.make_sound()
In [23]:
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>')
Out[23]:
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:
super built-in function.See https://rhettinger.wordpress.com/2011/05/26/super-considered-super/
In [24]:
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))
In [25]:
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>')
Out[25]:
make_sound.Java and C++ this is done more formally through Interfaces and Abstract Base Classes, respectively, plus inheritance.make_sound is called duck typing.
In [26]:
# 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())
Duck typing is used throughout Python. Indeed it's what enables the "Python Data Model"
Python data model.__repr__ and __str____repr__ and __str__ methods. objects methods when these are not defined.__repr__ and __str__. 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.
In [27]:
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)
In [28]:
r = Animal("David")
r
Out[28]:
In [29]:
print(r)
In [30]:
repr(r)
Out[30]: