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. object
s 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]: