Python OOP (Object Oriented Programming) ?
Python OOP (Object Oriented Programming) ?
Python OOP (Object Oriented Programming) ?
Object Oriented Programming (OOP) tends to be one of the major obstacles for beginners when
they are first starting to learn Python.
There are many, many tutorials and lessons covering OOP so feel free to Google search other
lessons, and I have also put some links to other useful tutorials online at the bottom of this
Notebook.
For this lesson we will construct our knowledge of OOP in Python by building on the following
topics:
Objects
Using the class keyword
Creating class attributes
Creating methods in a class
Learning about Inheritance
Learning about Polymorphism
Learning about Special Methods for classes
Lets start the lesson by remembering about the Basic Python Objects. For example:
In [14]: lst.count(2)
1
Out[14]:
What we will basically be doing in this lecture is exploring how we could create an Object type like a
list. We've already learned about how to create functions. So let's explore Objects in general:
Objects
In Python, everything is an object. Remember from previous lectures we can use type() to check the
type of object something is:
In [15]: print(type(1))
print(type([]))
print(type(()))
print(type({}))
<class 'int'>
<class 'list'>
<class 'tuple'>
<class 'dict'>
So we know all these things are objects, so how can we create our own Object types? That is where
the class keyword comes in.
class
User defined objects are created using the class keyword. The class is a blueprint that defines
the nature of a future object. From classes we can construct instances. An instance is a specific
object created from a particular class. For example, above we created the object lst which was
an instance of a list object.
class Sample:
pass
# Instance of Sample
x = Sample()
print(type(x))
<class '__main__.Sample'>
By convention we give classes a name that starts with a capital letter. Note how x is now the
reference to our new instance of a Sample class. In other words, we instantiate the Sample class.
Inside of the class we currently just have pass. But we can define class attributes and methods.
For example, we can create a class called Dog. An attribute of a dog may be its breed or its name,
while a method of a dog may be defined by a .bark() method which returns a sound.
Attributes
self.attribute = something
__init__()
def __init__(self,breed):
self.breed = breed
sam = Dog(breed='Lab')
frank = Dog(breed='Huskie')
__init__()
Each attribute in a class definition begins with a reference to the instance object. It is by convention
named self. The breed is the argument. The value is passed during the class instantiation.
self.breed = breed
Now we have created two instances of the Dog class. With two breed types, we can then access
these attributes like this:
In [18]: sam.breed
'Lab'
Out[18]:
In [19]: frank.breed
'Huskie'
Out[19]:
Note how we don't have any parentheses after breed; this is because it is an attribute and doesn't
take any arguments.
In Python there are also class object attributes. These Class Object Attributes are the same for any
instance of the class. For example, we could create the attribute species for the Dog class. Dogs,
regardless of their breed, name, or other attributes, will always be mammals. We apply this logic in
the following manner:
species = 'mammal'
def __init__(self,breed,name):
self.breed = breed
self.name = name
In [22]: sam.name
'Sam'
Out[22]:
Note that the Class Object Attribute is defined outside of any methods in the class. Also by
convention, we place them first before the init.
In [23]: sam.species
'mammal'
Out[23]:
Methods
Methods are functions defined inside the body of a class. They are used to perform operations with
the attributes of our objects. Methods are a key concept of the OOP paradigm. They are essential to
dividing responsibilities in programming, especially in large applications.
You can basically think of methods as functions acting on an Object that take the Object itself into
account through its self argument.
pi = 3.14
self.radius = radius
self.radius = new_radius
def getCircumference(self):
c = Circle()
Radius is: 1
Area is: 3.14
Circumference is: 6.28
_ In t he init met hod above, in order to calculat e t he area at t ribut e, we had to call Circle.pi.
T his is because t he object does not yet have its own .pi attribute, so we call the Class Object
Attribute pi instead.
In the setRadius method, however, we'll be working with an existing Circle object that does have its
own pi attribute. Here we can use either Circle.pi or self.pi.
Now let's change the radius and see how that affects our Circle object:
In [25]: c.setRadius(2)
Radius is: 2
Area is: 12.56
Circumference is: 12.56
Great! Notice how we used self. notation to reference attributes of the class within the method calls.
Review how the code above works and try creating your own method.
Inheritance
Inheritance is a way to form new classes using classes that have already been defined. The newly
formed classes are called derived classes, the classes that we derive from are called base classes.
Important benefits of inheritance are code reuse and reduction of complexity of a program. The
derived classes (descendants) override or extend the functionality of base classes (ancestors).
Let's see an example by incorporating our previous work on the Dog class:
def __init__(self):
print("Animal created")
def whoAmI(self):
print("Animal")
def eat(self):
print("Eating")
class Dog(Animal):
def __init__(self):
Animal.__init__(self)
print("Dog created")
def whoAmI(self):
print("Dog")
def bark(self):
print("Woof!")
In [27]: d = Dog()
Animal created
Dog created
In [28]: d.whoAmI()
Dog
In [29]: d.eat()
Eating
In [30]: d.bark()
Woof!
In this example, we have two classes: Animal and Dog. The Animal is the base class, the Dog is the
derived class.
The derived class inherits the functionality of the base class.
Finally, the derived class extends the functionality of the base class, by defining a new bark()
method.
Polymorphism
We've learned that while functions can take in different arguments, methods belong to the objects
they act on. In Python, polymorphism refers to the way in which different object classes can share
the same method name, and those methods can be called from the same place even though a
variety of different objects might be passed in. The best way to explain this is by example:
self.name = name
def speak(self):
class Cat:
self.name = name
def speak(self):
niko = Dog('Niko')
felix = Cat('Felix')
print(niko.speak())
print(felix.speak())
Here we have a Dog class and a Cat class, and each has a .speak() method. When called, each
object's .speak() method returns a result unique to the object.
There a few different ways to demonstrate polymorphism. First, with a for loop:
print(pet.speak())
print(pet.speak())
pet_speak(niko)
pet_speak(felix)
In both cases we were able to pass in different object types, and we obtained object-specific results
from the same mechanism.
A more common practice is to use abstract classes and inheritance. An abstract class is one that
never expects to be instantiated. For example, we will never have an Animal object, only Dog and
Cat objects, although Dogs and Cats are derived from Animals:
self.name = name
class Dog(Animal):
def speak(self):
class Cat(Animal):
def speak(self):
fido = Dog('Fido')
isis = Cat('Isis')
print(fido.speak())
print(isis.speak())
opening different file types - different tools are needed to display Word, pdf and Excel files
adding different objects - the + operator performs arithmetic and concatenation
Special Methods
Finally let's go over special methods. Classes in Python can implement certain operations with
special method names. These methods are not actually called directly but by Python specific
language syntax. For example let's create a Book class:
self.title = title
self.author = author
self.pages = pages
def __str__(self):
def __len__(self):
return self.pages
def __del__(self):
#Special Methods
print(book)
print(len(book))
del book
A book is created
Title: Python Rocks!, author: Jose Portilla, pages: 159
159
A book is destroyed
These special methods are defined by their use of underscores. They allow us to use Python specific
functions on objects created through our class.
Great! After this lecture you should have a basic understanding of how to create your own objects
with class in Python. You will be utilizing this heavily in your next milestone project!
Multiple Inheritance
The self keyword
Method Resolution Order (MRO)
Python's built-in super() function
Inheritance Revisited
Recall that with Inheritance, one or more derived classes can inherit attributes and methods from a
base class. This reduces duplication, and means that any changes made to the base class will
automatically translate to derived classes. As a review:
self.name = name
class Dog(Animal):
def speak(self):
class Cat(Animal):
def speak(self):
fido = Dog('Fido')
isis = Cat('Isis')
print(fido.speak())
print(isis.speak())
In this example, the derived classes did not need their own __init__ methods because the base
class __init__ gets called automatically. However, if you do define an __init__ in the
derived class, this will override the base:
def __init__(self,name,legs):
self.name = name
self.legs = legs
class Bear(Animal):
def __init__(self,name,legs=4,hibernate='yes'):
self.name = name
self.legs = legs
self.hibernate = hibernate
This is inefficient - why inherit from Animal if we can't use its constructor? The answer is to call the
Animal __init__ inside our own __init__ .
def __init__(self,name,legs):
self.name = name
self.legs = legs
class Bear(Animal):
def __init__(self,name,legs=4,hibernate='yes'):
Animal.__init__(self,name,legs)
self.hibernate = hibernate
yogi = Bear('Yogi')
print(yogi.name)
print(yogi.legs)
print(yogi.hibernate)
Yogi
4
yes
Multiple Inheritance
Sometimes it makes sense for a derived class to inherit qualities from two or more base classes.
Python allows for this with multiple inheritance.
def __init__(self,wheels=4):
self.wheels = wheels
# We'll say that all cars, no matter their engine, have four wheels by
class Gasoline(Car):
def __init__(self,engine='Gasoline',tank_cap=20):
Car.__init__(self)
self.engine = engine
self.tank = 0
def refuel(self):
self.tank = self.tank_cap
class Electric(Car):
def __init__(self,engine='Electric',kWh_cap=60):
Car.__init__(self)
self.engine = engine
def recharge(self):
self.kWh = self.kWh_cap
So what happens if we have an object that shares properties of both Gasolines and Electrics? We
can create a derived class that inherits from both!
def __init__(self,engine='Hybrid',tank_cap=11,kWh_cap=5):
Gasoline.__init__(self,engine,tank_cap)
Electric.__init__(self,engine,kWh_cap)
prius = Hybrid()
print(prius.tank)
print(prius.kWh)
0
0
In [6]: prius.recharge()
print(prius.kWh)
We've seen the word "self" show up in almost every example. What's the deal? The answer is, Python
uses self to find the right set of attributes and methods to apply to an object. When we say:
prius.recharge()
What really happens is that Python first looks up the class belonging to prius (Hybrid), and then
passes prius to the Hybrid.recharge() method.
Hybrid.recharge(prius)
but shorter and more intuitive!
Things get complicated when you have several base classes and levels of inheritance. This is
resolved using Method Resolution Order - a formal plan that Python follows when running object
methods.
To illustrate, if classes B and C each derive from A, and class D derives from both B and C, which
class is "first in line" when a method is called on D?
In [7]: class A:
num = 4
class B(A):
pass
class C(A):
num = 5
class D(B,C):
pass
A
num=4
/ \
/ \
B C
pass num=5
\ /
\ /
D
pass
Here num is a class attribute belonging to all four classes. So what happens if we call D.num ?
In [8]: D.num
5
Out[8]:
You would think that D.num would follow B up to A and return 4. Instead, Python obeys the first
method in the chain that defines num. The order followed is [D, B, C, A, object] where
object is Python's base object class.
In our example, the first class to define and/or override a previously defined num is C .
super()
Python's built-in super() function provides a shortcut for calling base classes, because it
automatically follows Method Resolution Order.
In its simplest form with single inheritance, super() can be used in place of the base class name
:
def __init__(self,x,y):
self.x = x
self.y = y
class MyDerivedClass(MyBaseClass):
def __init__(self,x,y,z):
super().__init__(x,y)
self.z = z
In a more dynamic form, with multiple inheritance like the "diamond diagram" shown above,
super() can be used to properly manage method definitions:
In [10]: class A:
def truth(self):
class B(A):
pass
class C(A):
def truth(self):
def truth(self,num):
if num%2 == 0:
return A.truth(self)
else:
return super().truth()
d = D()
d.truth(6)
In [12]: d.truth(5)
In the above example, if we pass an even number to d.truth() , we'll believe the A version of
.truth() and run with it. Otherwise, follow the MRO and return the more general case.
Great! Now you should have a much deeper understanding of Object Oriented Programming!
Thank You