Key Word(s): Object oriented programming, Classes
General Python Guidelines¶
Now that you're starting to write some Python code, you should be aware of some useful guidelines and resources:
from IPython.display import HTML
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.
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;
}
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 (called the Application Progamming Interface, API) and use it.
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 (Application Progamming Interface, API) and use it.
In a similar way, we want to encapsulate our data: we don't 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.
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 (Application Programming Interface, API) and use it.
In a similar way, we want to encapsulate our data: we don't 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, let's think of tuples.
# constructor
def Complex(a, b):
return (a, b)
# methods
def real(c):
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))
But things aren't hidden so I can get through the interface:
c1[0]
Because I used a tuple, and a tuple is immutable, I can't change this complex number once it's created.
c1[0] = 2
Objects thru closures¶
Let's try an implementation that uses a closure to capture the value of arguments.
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=="both":
return "{0}+{1}i".format(a, b)
return dispatch
z = Complex2(1,2)
print(z("real"), " ", z("imag"), " ", z("both"))
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¶
def Complex3(a, b):
def dispatch(message, value=None):
nonlocal a, b
if message=='set_real' and value != None:
a = value
elif message=='set_imag' and value != None:
b = value
elif message=="real":
return a
elif message=='imag':
return b
elif message=="both":
return "{0}+{1}i".format(a, b)
return dispatch
c3 = Complex3(1,2)
print(c3("real"), " ", c3("imag"), " ", c3("both"))
def Complex3(a, b):
def dispatch(message, value=None):
nonlocal a, b
if message=='set_real' and value != None:
a = value
elif message=='set_imag' and value != None:
b = value
elif message=="real":
return a
elif message=='imag':
return b
elif message=="both":
return "{0}+{1}i".format(a, b)
return dispatch
c3('set_real', 2)
print(c3("real"), " ", c3("imag"), " ", c3("both"))
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.
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.
HTML('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"> ')
c1 = ComplexClass(1,2)
print(c1)
print(c1.real)
print(vars(c1), " ",type(c1))
c1.real = 5.0
print(c1, " ", c1.real, " ", c1.imaginary)
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¶
class Animal(): # Animal is the superclass (a.k.a the base class).
def __init__(self, name):
self.name = name
def make_sound(self):
raise NotImplementedError
# Dog and Cat are both subclasses
# (a.k.a derived classes) of the Animal superclass.
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"
Using the Animal
class¶
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)
print(Dog.make_sound)
print(a1.make_sound())
print('----')
print(Dog.make_sound(a1))
Dog.make_sound()
How does this all work?¶
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"
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('--------')
HTML('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"> ')
Calling a superclass initializer¶
- Say we don't want to do all the work of setting the name variable in the subclasses.
Calling a superclass 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.
Calling a superclass 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.
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 {0} as {1}".format(self.name, self.animaltype))
class Cat(Animal):
pass
a1 = Mouse("Tom")
print(vars(a1))
a2 = Cat("Jerry")
print(vars(a2))
HTML('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"> ')
Recap¶
What you've learned to this point:
- Python classes
- Instance methods
- Superclasses & subclasses and inheritance & polymorphism
- Superclass initializers
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
andC++
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."
# 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())
A Few Comments on Duck Typing¶
- When using duck typing, you are specifying an implicit interface.
- This is possible in dynamically typed languages (but also in some statically typed languages like C++ [thru templates]).
- Duck typing can speed up the short-term development process.
- But beware! It can hinder long-term progress, at least in part due to the specification of an implicit interface.
- "Can anyone remember what we were thinking here?"
- When using duck typing, be sure to include extensive tests.